codeceptjs 4.0.0-beta.2 → 4.0.0-beta.4
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.
- package/README.md +2 -2
- package/bin/codecept.js +84 -81
- package/lib/actor.js +13 -13
- package/lib/ai.js +10 -13
- package/lib/assert/empty.js +20 -21
- package/lib/assert/equal.js +37 -39
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +46 -47
- package/lib/assert/throws.js +13 -11
- package/lib/assert/truth.js +19 -22
- package/lib/assert.js +4 -2
- package/lib/cli.js +56 -49
- package/lib/codecept.js +145 -155
- package/lib/colorUtils.js +3 -3
- package/lib/command/configMigrate.js +58 -52
- package/lib/command/definitions.js +88 -89
- package/lib/command/dryRun.js +79 -81
- package/lib/command/generate.js +197 -188
- package/lib/command/gherkin/init.js +27 -16
- package/lib/command/gherkin/snippets.js +21 -21
- package/lib/command/gherkin/steps.js +8 -8
- package/lib/command/info.js +40 -38
- package/lib/command/init.js +290 -288
- package/lib/command/interactive.js +32 -32
- package/lib/command/list.js +26 -26
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +3 -3
- package/lib/command/run-multiple/run.js +6 -2
- package/lib/command/run-multiple.js +113 -93
- package/lib/command/run-rerun.js +20 -25
- package/lib/command/run-workers.js +64 -66
- package/lib/command/run.js +26 -29
- package/lib/command/utils.js +80 -65
- package/lib/command/workers/runTests.js +11 -12
- package/lib/config.js +10 -9
- package/lib/container.js +40 -48
- package/lib/data/context.js +60 -59
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +29 -29
- package/lib/data/table.js +26 -20
- package/lib/event.js +163 -167
- package/lib/heal.js +14 -18
- package/lib/helper/AI.js +130 -41
- package/lib/helper/ApiDataFactory.js +74 -70
- package/lib/helper/Appium.js +416 -388
- package/lib/helper/ExpectHelper.js +40 -48
- package/lib/helper/FileSystem.js +80 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +51 -51
- package/lib/helper/JSONResponse.js +65 -62
- package/lib/helper/Mochawesome.js +28 -28
- package/lib/helper/Nightmare.js +664 -571
- package/lib/helper/Playwright.js +1367 -1222
- package/lib/helper/Protractor.js +663 -635
- package/lib/helper/Puppeteer.js +1232 -1132
- package/lib/helper/REST.js +183 -68
- package/lib/helper/SoftExpectHelper.js +2 -2
- package/lib/helper/TestCafe.js +490 -486
- package/lib/helper/WebDriver.js +1246 -1297
- package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
- package/lib/helper/errors/ConnectionRefused.js +1 -1
- package/lib/helper/errors/ElementAssertion.js +2 -2
- package/lib/helper/errors/ElementNotFound.js +2 -2
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +1 -1
- package/lib/helper/extras/Console.js +1 -1
- package/lib/helper/extras/PlaywrightPropEngine.js +4 -4
- package/lib/helper/extras/PlaywrightReactVueLocator.js +1 -1
- package/lib/helper/extras/PlaywrightRestartOpts.js +21 -18
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +3 -3
- package/lib/helper/network/actions.js +14 -7
- package/lib/helper/network/utils.js +4 -3
- package/lib/helper/scripts/blurElement.js +1 -1
- package/lib/helper/scripts/focusElement.js +1 -1
- package/lib/helper/scripts/highlightElement.js +1 -1
- package/lib/helper/scripts/isElementClickable.js +1 -1
- package/lib/helper/testcafe/testControllerHolder.js +1 -1
- package/lib/helper/testcafe/testcafe-utils.js +7 -8
- package/lib/helper.js +1 -3
- package/lib/history.js +6 -5
- package/lib/hooks.js +6 -6
- package/lib/html.js +7 -7
- package/lib/index.js +25 -41
- package/lib/interfaces/bdd.js +47 -64
- package/lib/interfaces/featureConfig.js +19 -19
- package/lib/interfaces/gherkin.js +124 -118
- package/lib/interfaces/scenarioConfig.js +29 -29
- package/lib/listener/artifacts.js +9 -9
- package/lib/listener/config.js +24 -24
- package/lib/listener/exit.js +12 -12
- package/lib/listener/helpers.js +42 -42
- package/lib/listener/mocha.js +11 -11
- package/lib/listener/retry.js +32 -30
- package/lib/listener/steps.js +50 -53
- package/lib/listener/timeout.js +54 -54
- package/lib/locator.js +7 -11
- package/lib/mochaFactory.js +18 -15
- package/lib/output.js +19 -15
- package/lib/parser.js +15 -12
- package/lib/pause.js +45 -38
- package/lib/plugin/allure.js +15 -15
- package/lib/plugin/autoDelay.js +29 -37
- package/lib/plugin/autoLogin.js +70 -65
- package/lib/plugin/commentStep.js +18 -18
- package/lib/plugin/coverage.js +112 -67
- package/lib/plugin/customLocator.js +21 -20
- package/lib/plugin/debugErrors.js +24 -24
- package/lib/plugin/eachElement.js +38 -38
- package/lib/plugin/fakerTransform.js +6 -6
- package/lib/plugin/heal.js +67 -108
- package/lib/plugin/pauseOnFail.js +11 -11
- package/lib/plugin/retryFailedStep.js +32 -39
- package/lib/plugin/retryTo.js +46 -40
- package/lib/plugin/screenshotOnFail.js +109 -87
- package/lib/plugin/selenoid.js +131 -118
- package/lib/plugin/standardActingHelpers.js +2 -8
- package/lib/plugin/stepByStepReport.js +110 -91
- package/lib/plugin/stepTimeout.js +24 -23
- package/lib/plugin/subtitles.js +34 -35
- package/lib/plugin/tryTo.js +40 -30
- package/lib/plugin/wdio.js +78 -75
- package/lib/recorder.js +14 -17
- package/lib/rerun.js +11 -10
- package/lib/scenario.js +25 -23
- package/lib/secret.js +4 -3
- package/lib/session.js +10 -10
- package/lib/step.js +12 -9
- package/lib/store.js +2 -3
- package/lib/transform.js +1 -1
- package/lib/translation.js +7 -8
- package/lib/ui.js +12 -14
- package/lib/utils.js +70 -72
- package/lib/within.js +10 -10
- package/lib/workerStorage.js +27 -25
- package/lib/workers.js +29 -33
- package/package.json +67 -68
- package/translations/de-DE.js +2 -1
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +9 -13
- package/translations/it-IT.js +1 -1
- package/translations/ja-JP.js +1 -1
- package/translations/pl-PL.js +1 -1
- package/translations/pt-BR.js +1 -1
- package/translations/ru-RU.js +1 -1
- package/translations/zh-CN.js +1 -1
- package/translations/zh-TW.js +1 -1
- package/typings/index.d.ts +423 -65
- package/typings/promiseBasedTypes.d.ts +41 -172
- package/typings/types.d.ts +43 -178
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
- package/lib/helper/MockServer.js +0 -223
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
const axios = require('axios')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const fsExtra = require('fs-extra')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
const Helper = require('@codeceptjs/helper')
|
|
7
|
+
const { v4: uuidv4 } = require('uuid')
|
|
8
|
+
const promiseRetry = require('promise-retry')
|
|
9
|
+
const Locator = require('../locator')
|
|
10
|
+
const recorder = require('../recorder')
|
|
11
|
+
const store = require('../store')
|
|
12
|
+
const stringIncludes = require('../assert/include').includes
|
|
13
|
+
const { urlEquals } = require('../assert/equal')
|
|
14
|
+
const { equals } = require('../assert/equal')
|
|
15
|
+
const { empty } = require('../assert/empty')
|
|
16
|
+
const { truth } = require('../assert/truth')
|
|
17
|
+
const isElementClickable = require('./scripts/isElementClickable')
|
|
18
|
+
const {
|
|
18
19
|
xpathLocator,
|
|
19
20
|
ucfirst,
|
|
20
21
|
fileExists,
|
|
@@ -27,33 +28,32 @@ import {
|
|
|
27
28
|
isModifierKey,
|
|
28
29
|
requireWithFallback,
|
|
29
30
|
normalizeSpacesInString,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
import { blurElement } from './scripts/blurElement';
|
|
40
|
-
import { focusElement } from './scripts/focusElement';
|
|
41
|
-
|
|
42
|
-
import {
|
|
31
|
+
} = require('../utils')
|
|
32
|
+
const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
|
|
33
|
+
const ElementNotFound = require('./errors/ElementNotFound')
|
|
34
|
+
const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused')
|
|
35
|
+
const Popup = require('./extras/Popup')
|
|
36
|
+
const Console = require('./extras/Console')
|
|
37
|
+
const { highlightElement } = require('./scripts/highlightElement')
|
|
38
|
+
const { blurElement } = require('./scripts/blurElement')
|
|
39
|
+
const {
|
|
43
40
|
dontSeeElementError,
|
|
44
41
|
seeElementError,
|
|
45
42
|
dontSeeElementInDOMError,
|
|
46
43
|
seeElementInDOMError,
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
} = require('./errors/ElementAssertion')
|
|
45
|
+
const {
|
|
46
|
+
dontSeeTraffic,
|
|
47
|
+
seeTraffic,
|
|
48
|
+
grabRecordedNetworkTraffics,
|
|
49
|
+
stopRecordingTraffic,
|
|
50
|
+
flushNetworkTraffics,
|
|
51
|
+
} = require('./network/actions')
|
|
52
|
+
|
|
53
|
+
let puppeteer
|
|
54
|
+
let perfTiming
|
|
55
|
+
const popupStore = new Popup()
|
|
56
|
+
const consoleLogStore = new Console()
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* ## Configuration
|
|
@@ -85,7 +85,7 @@ const consoleLogStore = new Console();
|
|
|
85
85
|
* @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
|
|
86
86
|
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
|
|
87
87
|
*/
|
|
88
|
-
const config = {}
|
|
88
|
+
const config = {}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Uses [Google Chrome's Puppeteer](https://github.com/GoogleChrome/puppeteer) library to run tests inside headless Chrome.
|
|
@@ -222,29 +222,29 @@ const config = {};
|
|
|
222
222
|
*/
|
|
223
223
|
class Puppeteer extends Helper {
|
|
224
224
|
constructor(config) {
|
|
225
|
-
super(config)
|
|
225
|
+
super(config)
|
|
226
226
|
|
|
227
|
-
puppeteer = requireWithFallback('puppeteer', 'puppeteer-core')
|
|
227
|
+
puppeteer = requireWithFallback('puppeteer', 'puppeteer-core')
|
|
228
228
|
// set defaults
|
|
229
|
-
this.isRemoteBrowser = false
|
|
230
|
-
this.isRunning = false
|
|
231
|
-
this.isAuthenticated = false
|
|
232
|
-
this.sessionPages = {}
|
|
233
|
-
this.activeSessionName = ''
|
|
229
|
+
this.isRemoteBrowser = false
|
|
230
|
+
this.isRunning = false
|
|
231
|
+
this.isAuthenticated = false
|
|
232
|
+
this.sessionPages = {}
|
|
233
|
+
this.activeSessionName = ''
|
|
234
234
|
|
|
235
235
|
// for network stuff
|
|
236
|
-
this.requests = []
|
|
237
|
-
this.recording = false
|
|
238
|
-
this.recordedAtLeastOnce = false
|
|
236
|
+
this.requests = []
|
|
237
|
+
this.recording = false
|
|
238
|
+
this.recordedAtLeastOnce = false
|
|
239
239
|
|
|
240
240
|
// for websocket messages
|
|
241
|
-
this.webSocketMessages = []
|
|
242
|
-
this.recordingWebSocketMessages = false
|
|
243
|
-
this.recordedWebSocketMessagesAtLeastOnce = false
|
|
244
|
-
this.cdpSession = null
|
|
241
|
+
this.webSocketMessages = []
|
|
242
|
+
this.recordingWebSocketMessages = false
|
|
243
|
+
this.recordedWebSocketMessagesAtLeastOnce = false
|
|
244
|
+
this.cdpSession = null
|
|
245
245
|
|
|
246
246
|
// override defaults with config
|
|
247
|
-
this._setConfig(config)
|
|
247
|
+
this._setConfig(config)
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
_validateConfig(config) {
|
|
@@ -265,156 +265,161 @@ class Puppeteer extends Helper {
|
|
|
265
265
|
show: false,
|
|
266
266
|
defaultPopupAction: 'accept',
|
|
267
267
|
highlightElement: false,
|
|
268
|
-
}
|
|
268
|
+
}
|
|
269
269
|
|
|
270
|
-
return Object.assign(defaults, config)
|
|
270
|
+
return Object.assign(defaults, config)
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
_getOptions(config) {
|
|
274
|
-
return config.browser === 'firefox'
|
|
274
|
+
return config.browser === 'firefox'
|
|
275
|
+
? Object.assign(this.options.firefox, { product: 'firefox' })
|
|
276
|
+
: this.options.chrome
|
|
275
277
|
}
|
|
276
278
|
|
|
277
279
|
_setConfig(config) {
|
|
278
|
-
this.options = this._validateConfig(config)
|
|
280
|
+
this.options = this._validateConfig(config)
|
|
279
281
|
this.puppeteerOptions = {
|
|
280
282
|
headless: !this.options.show,
|
|
281
283
|
...this._getOptions(config),
|
|
282
|
-
}
|
|
283
|
-
if (this.puppeteerOptions.headless) this.puppeteerOptions.headless = 'new'
|
|
284
|
-
this.isRemoteBrowser = !!this.puppeteerOptions.browserWSEndpoint
|
|
285
|
-
popupStore.defaultAction = this.options.defaultPopupAction
|
|
284
|
+
}
|
|
285
|
+
if (this.puppeteerOptions.headless) this.puppeteerOptions.headless = 'new'
|
|
286
|
+
this.isRemoteBrowser = !!this.puppeteerOptions.browserWSEndpoint
|
|
287
|
+
popupStore.defaultAction = this.options.defaultPopupAction
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
static _config() {
|
|
289
291
|
return [
|
|
290
292
|
{ name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
|
|
291
293
|
{
|
|
292
|
-
name: 'show',
|
|
294
|
+
name: 'show',
|
|
295
|
+
message: 'Show browser window',
|
|
296
|
+
default: true,
|
|
297
|
+
type: 'confirm',
|
|
293
298
|
},
|
|
294
299
|
{
|
|
295
|
-
name: 'windowSize',
|
|
300
|
+
name: 'windowSize',
|
|
301
|
+
message: 'Browser viewport size',
|
|
302
|
+
default: '1200x900',
|
|
296
303
|
},
|
|
297
|
-
]
|
|
304
|
+
]
|
|
298
305
|
}
|
|
299
306
|
|
|
300
307
|
static _checkRequirements() {
|
|
301
308
|
try {
|
|
302
|
-
requireWithFallback('puppeteer', 'puppeteer-core')
|
|
309
|
+
requireWithFallback('puppeteer', 'puppeteer-core')
|
|
303
310
|
} catch (e) {
|
|
304
|
-
return ['puppeteer']
|
|
311
|
+
return ['puppeteer']
|
|
305
312
|
}
|
|
306
313
|
}
|
|
307
314
|
|
|
308
|
-
_init() {
|
|
309
|
-
}
|
|
315
|
+
_init() {}
|
|
310
316
|
|
|
311
317
|
_beforeSuite() {
|
|
312
318
|
if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
|
|
313
|
-
this.debugSection('Session', 'Starting singleton browser session')
|
|
314
|
-
return this._startBrowser()
|
|
319
|
+
this.debugSection('Session', 'Starting singleton browser session')
|
|
320
|
+
return this._startBrowser()
|
|
315
321
|
}
|
|
316
322
|
}
|
|
317
323
|
|
|
318
324
|
async _before(test) {
|
|
319
|
-
this.sessionPages = {}
|
|
320
|
-
this.currentRunningTest = test
|
|
325
|
+
this.sessionPages = {}
|
|
326
|
+
this.currentRunningTest = test
|
|
321
327
|
recorder.retry({
|
|
322
328
|
retries: process.env.FAILED_STEP_RETRIES || 3,
|
|
323
|
-
when: err => {
|
|
324
|
-
if (!err || typeof
|
|
325
|
-
return false
|
|
329
|
+
when: (err) => {
|
|
330
|
+
if (!err || typeof err.message !== 'string') {
|
|
331
|
+
return false
|
|
326
332
|
}
|
|
327
333
|
// ignore context errors
|
|
328
|
-
return err.message.includes('context')
|
|
334
|
+
return err.message.includes('context')
|
|
329
335
|
},
|
|
330
|
-
})
|
|
331
|
-
if (this.options.restart && !this.options.manualStart) return this._startBrowser()
|
|
332
|
-
if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
|
|
333
|
-
return this.browser
|
|
336
|
+
})
|
|
337
|
+
if (this.options.restart && !this.options.manualStart) return this._startBrowser()
|
|
338
|
+
if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
|
|
339
|
+
return this.browser
|
|
334
340
|
}
|
|
335
341
|
|
|
336
342
|
async _after() {
|
|
337
|
-
if (!this.isRunning) return
|
|
343
|
+
if (!this.isRunning) return
|
|
338
344
|
|
|
339
345
|
// close other sessions
|
|
340
|
-
const contexts = this.browser.browserContexts()
|
|
341
|
-
const defaultCtx = contexts.shift()
|
|
346
|
+
const contexts = this.browser.browserContexts()
|
|
347
|
+
const defaultCtx = contexts.shift()
|
|
342
348
|
|
|
343
|
-
await Promise.all(contexts.map(c => c.close()))
|
|
349
|
+
await Promise.all(contexts.map((c) => c.close()))
|
|
344
350
|
|
|
345
351
|
if (this.options.restart) {
|
|
346
|
-
this.isRunning = false
|
|
347
|
-
return this._stopBrowser()
|
|
352
|
+
this.isRunning = false
|
|
353
|
+
return this._stopBrowser()
|
|
348
354
|
}
|
|
349
355
|
|
|
350
356
|
// ensure this.page is from default context
|
|
351
357
|
if (this.page) {
|
|
352
|
-
const existingPages = defaultCtx.targets().filter(t => t.type() === 'page')
|
|
353
|
-
await this._setPage(await existingPages[0].page())
|
|
358
|
+
const existingPages = defaultCtx.targets().filter((t) => t.type() === 'page')
|
|
359
|
+
await this._setPage(await existingPages[0].page())
|
|
354
360
|
}
|
|
355
361
|
|
|
356
|
-
if (this.options.keepBrowserState) return
|
|
362
|
+
if (this.options.keepBrowserState) return
|
|
357
363
|
|
|
358
364
|
if (!this.options.keepCookies) {
|
|
359
|
-
this.debugSection('Session', 'cleaning cookies and localStorage')
|
|
360
|
-
await this.clearCookie()
|
|
365
|
+
this.debugSection('Session', 'cleaning cookies and localStorage')
|
|
366
|
+
await this.clearCookie()
|
|
361
367
|
}
|
|
362
|
-
const currentUrl = await this.grabCurrentUrl()
|
|
368
|
+
const currentUrl = await this.grabCurrentUrl()
|
|
363
369
|
|
|
364
370
|
if (currentUrl.startsWith('http')) {
|
|
365
371
|
await this.executeScript('localStorage.clear();').catch((err) => {
|
|
366
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
367
|
-
})
|
|
372
|
+
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
373
|
+
})
|
|
368
374
|
await this.executeScript('sessionStorage.clear();').catch((err) => {
|
|
369
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
370
|
-
})
|
|
375
|
+
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
376
|
+
})
|
|
371
377
|
}
|
|
372
|
-
await this.closeOtherTabs()
|
|
373
|
-
return this.browser
|
|
378
|
+
await this.closeOtherTabs()
|
|
379
|
+
return this.browser
|
|
374
380
|
}
|
|
375
381
|
|
|
376
|
-
_afterSuite() {
|
|
377
|
-
}
|
|
382
|
+
_afterSuite() {}
|
|
378
383
|
|
|
379
384
|
_finishTest() {
|
|
380
|
-
if (!this.options.restart && this.isRunning) return this._stopBrowser()
|
|
385
|
+
if (!this.options.restart && this.isRunning) return this._stopBrowser()
|
|
381
386
|
}
|
|
382
387
|
|
|
383
388
|
_session() {
|
|
384
389
|
return {
|
|
385
390
|
start: async (name = '') => {
|
|
386
|
-
this.debugSection('Incognito Tab', 'opened')
|
|
387
|
-
this.activeSessionName = name
|
|
391
|
+
this.debugSection('Incognito Tab', 'opened')
|
|
392
|
+
this.activeSessionName = name
|
|
388
393
|
|
|
389
|
-
const bc = await this.browser.createBrowserContext()
|
|
390
|
-
await bc.newPage()
|
|
394
|
+
const bc = await this.browser.createBrowserContext()
|
|
395
|
+
await bc.newPage()
|
|
391
396
|
|
|
392
397
|
// Create a new page inside context.
|
|
393
|
-
return bc
|
|
398
|
+
return bc
|
|
394
399
|
},
|
|
395
400
|
stop: async () => {
|
|
396
401
|
// is closed by _after
|
|
397
402
|
},
|
|
398
403
|
loadVars: async (context) => {
|
|
399
|
-
const existingPages = context.targets().filter(t => t.type() === 'page')
|
|
400
|
-
this.sessionPages[this.activeSessionName] = await existingPages[0].page()
|
|
401
|
-
return this._setPage(this.sessionPages[this.activeSessionName])
|
|
404
|
+
const existingPages = context.targets().filter((t) => t.type() === 'page')
|
|
405
|
+
this.sessionPages[this.activeSessionName] = await existingPages[0].page()
|
|
406
|
+
return this._setPage(this.sessionPages[this.activeSessionName])
|
|
402
407
|
},
|
|
403
408
|
restoreVars: async (session) => {
|
|
404
|
-
this.withinLocator = null
|
|
409
|
+
this.withinLocator = null
|
|
405
410
|
|
|
406
411
|
if (!session) {
|
|
407
|
-
this.activeSessionName = ''
|
|
412
|
+
this.activeSessionName = ''
|
|
408
413
|
} else {
|
|
409
|
-
this.activeSessionName = session
|
|
414
|
+
this.activeSessionName = session
|
|
410
415
|
}
|
|
411
|
-
const defaultCtx = this.browser.defaultBrowserContext()
|
|
412
|
-
const existingPages = defaultCtx.targets().filter(t => t.type() === 'page')
|
|
413
|
-
await this._setPage(await existingPages[0].page())
|
|
416
|
+
const defaultCtx = this.browser.defaultBrowserContext()
|
|
417
|
+
const existingPages = defaultCtx.targets().filter((t) => t.type() === 'page')
|
|
418
|
+
await this._setPage(await existingPages[0].page())
|
|
414
419
|
|
|
415
|
-
return this._waitForAction()
|
|
420
|
+
return this._waitForAction()
|
|
416
421
|
},
|
|
417
|
-
}
|
|
422
|
+
}
|
|
418
423
|
}
|
|
419
424
|
|
|
420
425
|
/**
|
|
@@ -435,7 +440,7 @@ class Puppeteer extends Helper {
|
|
|
435
440
|
* @param {function} fn async function that is executed with Puppeteer as argument
|
|
436
441
|
*/
|
|
437
442
|
usePuppeteerTo(description, fn) {
|
|
438
|
-
return this._useTo(...arguments)
|
|
443
|
+
return this._useTo(...arguments)
|
|
439
444
|
}
|
|
440
445
|
|
|
441
446
|
/**
|
|
@@ -449,7 +454,7 @@ class Puppeteer extends Helper {
|
|
|
449
454
|
* ```
|
|
450
455
|
*/
|
|
451
456
|
amAcceptingPopups() {
|
|
452
|
-
popupStore.actionType = 'accept'
|
|
457
|
+
popupStore.actionType = 'accept'
|
|
453
458
|
}
|
|
454
459
|
|
|
455
460
|
/**
|
|
@@ -458,7 +463,7 @@ class Puppeteer extends Helper {
|
|
|
458
463
|
* libraries](http://jster.net/category/windows-modals-popups).
|
|
459
464
|
*/
|
|
460
465
|
acceptPopup() {
|
|
461
|
-
popupStore.assertPopupActionType('accept')
|
|
466
|
+
popupStore.assertPopupActionType('accept')
|
|
462
467
|
}
|
|
463
468
|
|
|
464
469
|
/**
|
|
@@ -472,23 +477,23 @@ class Puppeteer extends Helper {
|
|
|
472
477
|
* ```
|
|
473
478
|
*/
|
|
474
479
|
amCancellingPopups() {
|
|
475
|
-
popupStore.actionType = 'cancel'
|
|
480
|
+
popupStore.actionType = 'cancel'
|
|
476
481
|
}
|
|
477
482
|
|
|
478
483
|
/**
|
|
479
484
|
* Dismisses the active JavaScript popup, as created by window.alert|window.confirm|window.prompt.
|
|
480
485
|
*/
|
|
481
486
|
cancelPopup() {
|
|
482
|
-
popupStore.assertPopupActionType('cancel')
|
|
487
|
+
popupStore.assertPopupActionType('cancel')
|
|
483
488
|
}
|
|
484
489
|
|
|
485
490
|
/**
|
|
486
491
|
* {{> seeInPopup }}
|
|
487
492
|
*/
|
|
488
493
|
async seeInPopup(text) {
|
|
489
|
-
popupStore.assertPopupVisible()
|
|
490
|
-
const popupText = await popupStore.popup.message()
|
|
491
|
-
stringIncludes('text in popup').assert(text, popupText)
|
|
494
|
+
popupStore.assertPopupVisible()
|
|
495
|
+
const popupText = await popupStore.popup.message()
|
|
496
|
+
stringIncludes('text in popup').assert(text, popupText)
|
|
492
497
|
}
|
|
493
498
|
|
|
494
499
|
/**
|
|
@@ -496,25 +501,25 @@ class Puppeteer extends Helper {
|
|
|
496
501
|
* @param {object} page page to set
|
|
497
502
|
*/
|
|
498
503
|
async _setPage(page) {
|
|
499
|
-
page = await page
|
|
500
|
-
this._addPopupListener(page)
|
|
501
|
-
this._addErrorListener(page)
|
|
502
|
-
this.page = page
|
|
503
|
-
if (!page) return
|
|
504
|
-
page.setDefaultNavigationTimeout(this.options.getPageTimeout)
|
|
505
|
-
this.context = await this.page.$('body')
|
|
504
|
+
page = await page
|
|
505
|
+
this._addPopupListener(page)
|
|
506
|
+
this._addErrorListener(page)
|
|
507
|
+
this.page = page
|
|
508
|
+
if (!page) return
|
|
509
|
+
page.setDefaultNavigationTimeout(this.options.getPageTimeout)
|
|
510
|
+
this.context = await this.page.$('body')
|
|
506
511
|
if (this.options.browser === 'chrome') {
|
|
507
|
-
await page.bringToFront()
|
|
512
|
+
await page.bringToFront()
|
|
508
513
|
}
|
|
509
514
|
}
|
|
510
515
|
|
|
511
516
|
async _addErrorListener(page) {
|
|
512
517
|
if (!page) {
|
|
513
|
-
return
|
|
518
|
+
return
|
|
514
519
|
}
|
|
515
520
|
page.on('error', async (error) => {
|
|
516
|
-
console.error('Puppeteer page error', error)
|
|
517
|
-
})
|
|
521
|
+
console.error('Puppeteer page error', error)
|
|
522
|
+
})
|
|
518
523
|
}
|
|
519
524
|
|
|
520
525
|
/**
|
|
@@ -526,32 +531,32 @@ class Puppeteer extends Helper {
|
|
|
526
531
|
*/
|
|
527
532
|
_addPopupListener(page) {
|
|
528
533
|
if (!page) {
|
|
529
|
-
return
|
|
534
|
+
return
|
|
530
535
|
}
|
|
531
536
|
page.on('dialog', async (dialog) => {
|
|
532
|
-
popupStore.popup = dialog
|
|
533
|
-
const action = popupStore.actionType || this.options.defaultPopupAction
|
|
534
|
-
await this._waitForAction()
|
|
537
|
+
popupStore.popup = dialog
|
|
538
|
+
const action = popupStore.actionType || this.options.defaultPopupAction
|
|
539
|
+
await this._waitForAction()
|
|
535
540
|
|
|
536
541
|
switch (action) {
|
|
537
542
|
case 'accept':
|
|
538
|
-
return dialog.accept()
|
|
543
|
+
return dialog.accept()
|
|
539
544
|
|
|
540
545
|
case 'cancel':
|
|
541
|
-
return dialog.dismiss()
|
|
546
|
+
return dialog.dismiss()
|
|
542
547
|
|
|
543
548
|
default: {
|
|
544
|
-
throw new Error('Unknown popup action type. Only "accept" or "cancel" are accepted')
|
|
549
|
+
throw new Error('Unknown popup action type. Only "accept" or "cancel" are accepted')
|
|
545
550
|
}
|
|
546
551
|
}
|
|
547
|
-
})
|
|
552
|
+
})
|
|
548
553
|
}
|
|
549
554
|
|
|
550
555
|
/**
|
|
551
556
|
* Gets page URL including hash.
|
|
552
557
|
*/
|
|
553
558
|
async _getPageUrl() {
|
|
554
|
-
return this.executeScript(() => window.location.href)
|
|
559
|
+
return this.executeScript(() => window.location.href)
|
|
555
560
|
}
|
|
556
561
|
|
|
557
562
|
/**
|
|
@@ -564,128 +569,134 @@ class Puppeteer extends Helper {
|
|
|
564
569
|
*/
|
|
565
570
|
async grabPopupText() {
|
|
566
571
|
if (popupStore.popup) {
|
|
567
|
-
return popupStore.popup.message()
|
|
572
|
+
return popupStore.popup.message()
|
|
568
573
|
}
|
|
569
|
-
return null
|
|
574
|
+
return null
|
|
570
575
|
}
|
|
571
576
|
|
|
572
577
|
async _startBrowser() {
|
|
573
578
|
if (this.isRemoteBrowser) {
|
|
574
579
|
try {
|
|
575
|
-
this.browser = await puppeteer.connect(this.puppeteerOptions)
|
|
580
|
+
this.browser = await puppeteer.connect(this.puppeteerOptions)
|
|
576
581
|
} catch (err) {
|
|
577
582
|
if (err.toString().indexOf('ECONNREFUSED')) {
|
|
578
|
-
throw new RemoteBrowserConnectionRefused(err)
|
|
583
|
+
throw new RemoteBrowserConnectionRefused(err)
|
|
579
584
|
}
|
|
580
|
-
throw err
|
|
585
|
+
throw err
|
|
581
586
|
}
|
|
582
587
|
} else {
|
|
583
|
-
this.browser = await puppeteer.launch(this.puppeteerOptions)
|
|
588
|
+
this.browser = await puppeteer.launch(this.puppeteerOptions)
|
|
584
589
|
}
|
|
585
590
|
|
|
586
|
-
this.browser.on('targetcreated', target
|
|
587
|
-
|
|
588
|
-
|
|
591
|
+
this.browser.on('targetcreated', (target) =>
|
|
592
|
+
target
|
|
593
|
+
.page()
|
|
594
|
+
.then((page) => targetCreatedHandler.call(this, page))
|
|
595
|
+
.catch((e) => {
|
|
596
|
+
console.error('Puppeteer page error', e)
|
|
597
|
+
}),
|
|
598
|
+
)
|
|
589
599
|
this.browser.on('targetchanged', (target) => {
|
|
590
|
-
this.debugSection('Url', target.url())
|
|
591
|
-
})
|
|
600
|
+
this.debugSection('Url', target.url())
|
|
601
|
+
})
|
|
592
602
|
|
|
593
|
-
const existingPages = await this.browser.pages()
|
|
594
|
-
const mainPage = existingPages[0] || (await this.browser.newPage())
|
|
603
|
+
const existingPages = await this.browser.pages()
|
|
604
|
+
const mainPage = existingPages[0] || (await this.browser.newPage())
|
|
595
605
|
|
|
596
606
|
if (existingPages.length) {
|
|
597
607
|
// Run the handler as it will not be triggered if the page already exists
|
|
598
|
-
targetCreatedHandler.call(this, mainPage)
|
|
608
|
+
targetCreatedHandler.call(this, mainPage)
|
|
599
609
|
}
|
|
600
|
-
await this._setPage(mainPage)
|
|
601
|
-
await this.closeOtherTabs()
|
|
610
|
+
await this._setPage(mainPage)
|
|
611
|
+
await this.closeOtherTabs()
|
|
602
612
|
|
|
603
|
-
this.isRunning = true
|
|
613
|
+
this.isRunning = true
|
|
604
614
|
}
|
|
605
615
|
|
|
606
616
|
async _stopBrowser() {
|
|
607
|
-
this.withinLocator = null
|
|
608
|
-
this._setPage(null)
|
|
609
|
-
this.context = null
|
|
610
|
-
popupStore.clear()
|
|
611
|
-
this.isAuthenticated = false
|
|
612
|
-
await this.browser.close()
|
|
617
|
+
this.withinLocator = null
|
|
618
|
+
this._setPage(null)
|
|
619
|
+
this.context = null
|
|
620
|
+
popupStore.clear()
|
|
621
|
+
this.isAuthenticated = false
|
|
622
|
+
await this.browser.close()
|
|
613
623
|
if (this.isRemoteBrowser) {
|
|
614
|
-
await this.browser.disconnect()
|
|
624
|
+
await this.browser.disconnect()
|
|
615
625
|
}
|
|
616
626
|
}
|
|
617
627
|
|
|
618
628
|
async _evaluateHandeInContext(...args) {
|
|
619
|
-
const context = await this._getContext()
|
|
620
|
-
return context.evaluateHandle(...args)
|
|
629
|
+
const context = await this._getContext()
|
|
630
|
+
return context.evaluateHandle(...args)
|
|
621
631
|
}
|
|
622
632
|
|
|
623
633
|
async _withinBegin(locator) {
|
|
624
634
|
if (this.withinLocator) {
|
|
625
|
-
throw new Error(
|
|
635
|
+
throw new Error("Can't start within block inside another within block")
|
|
626
636
|
}
|
|
627
637
|
|
|
628
|
-
const frame = isFrameLocator(locator)
|
|
638
|
+
const frame = isFrameLocator(locator)
|
|
629
639
|
|
|
630
640
|
if (frame) {
|
|
631
641
|
if (Array.isArray(frame)) {
|
|
632
|
-
return this.switchTo(null)
|
|
633
|
-
|
|
642
|
+
return this.switchTo(null).then(() =>
|
|
643
|
+
frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()),
|
|
644
|
+
)
|
|
634
645
|
}
|
|
635
|
-
await this.switchTo(frame)
|
|
636
|
-
this.withinLocator = new Locator(frame)
|
|
637
|
-
return
|
|
646
|
+
await this.switchTo(frame)
|
|
647
|
+
this.withinLocator = new Locator(frame)
|
|
648
|
+
return
|
|
638
649
|
}
|
|
639
650
|
|
|
640
|
-
const els = await this._locate(locator)
|
|
641
|
-
assertElementExists(els, locator)
|
|
642
|
-
this.context = els[0]
|
|
651
|
+
const els = await this._locate(locator)
|
|
652
|
+
assertElementExists(els, locator)
|
|
653
|
+
this.context = els[0]
|
|
643
654
|
|
|
644
|
-
this.withinLocator = new Locator(locator)
|
|
655
|
+
this.withinLocator = new Locator(locator)
|
|
645
656
|
}
|
|
646
657
|
|
|
647
658
|
async _withinEnd() {
|
|
648
|
-
this.withinLocator = null
|
|
649
|
-
this.context = await this.page.mainFrame().$('body')
|
|
659
|
+
this.withinLocator = null
|
|
660
|
+
this.context = await this.page.mainFrame().$('body')
|
|
650
661
|
}
|
|
651
662
|
|
|
652
663
|
_extractDataFromPerformanceTiming(timing, ...dataNames) {
|
|
653
|
-
const navigationStart = timing.navigationStart
|
|
664
|
+
const navigationStart = timing.navigationStart
|
|
654
665
|
|
|
655
|
-
const extractedData = {}
|
|
666
|
+
const extractedData = {}
|
|
656
667
|
dataNames.forEach((name) => {
|
|
657
|
-
extractedData[name] = timing[name] - navigationStart
|
|
658
|
-
})
|
|
668
|
+
extractedData[name] = timing[name] - navigationStart
|
|
669
|
+
})
|
|
659
670
|
|
|
660
|
-
return extractedData
|
|
671
|
+
return extractedData
|
|
661
672
|
}
|
|
662
673
|
|
|
663
674
|
/**
|
|
664
675
|
* {{> amOnPage }}
|
|
665
676
|
*/
|
|
666
677
|
async amOnPage(url) {
|
|
667
|
-
if (
|
|
668
|
-
url = this.options.url + url
|
|
678
|
+
if (!/^\w+\:\/\//.test(url)) {
|
|
679
|
+
url = this.options.url + url
|
|
669
680
|
}
|
|
670
681
|
|
|
671
|
-
if (this.options.basicAuth &&
|
|
682
|
+
if (this.options.basicAuth && this.isAuthenticated !== true) {
|
|
672
683
|
if (url.includes(this.options.url)) {
|
|
673
|
-
await this.page.authenticate(this.options.basicAuth)
|
|
674
|
-
this.isAuthenticated = true
|
|
684
|
+
await this.page.authenticate(this.options.basicAuth)
|
|
685
|
+
this.isAuthenticated = true
|
|
675
686
|
}
|
|
676
687
|
}
|
|
677
688
|
|
|
678
689
|
if (this.options.trace) {
|
|
679
|
-
const fileName = `${`${global.output_dir}${path.sep}trace${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.json
|
|
680
|
-
const dir = path.dirname(fileName)
|
|
681
|
-
if (!fileExists(dir)) fs.mkdirSync(dir)
|
|
682
|
-
await this.page.tracing.start({ screenshots: true, path: fileName })
|
|
683
|
-
this.currentRunningTest.artifacts.trace = fileName
|
|
690
|
+
const fileName = `${`${global.output_dir}${path.sep}trace${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.json`
|
|
691
|
+
const dir = path.dirname(fileName)
|
|
692
|
+
if (!fileExists(dir)) fs.mkdirSync(dir)
|
|
693
|
+
await this.page.tracing.start({ screenshots: true, path: fileName })
|
|
694
|
+
this.currentRunningTest.artifacts.trace = fileName
|
|
684
695
|
}
|
|
685
696
|
|
|
686
|
-
await this.page.goto(url, { waitUntil: this.options.waitForNavigation })
|
|
697
|
+
await this.page.goto(url, { waitUntil: this.options.waitForNavigation })
|
|
687
698
|
|
|
688
|
-
const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)))
|
|
699
|
+
const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)))
|
|
689
700
|
|
|
690
701
|
perfTiming = this._extractDataFromPerformanceTiming(
|
|
691
702
|
performanceTiming,
|
|
@@ -693,9 +704,9 @@ class Puppeteer extends Helper {
|
|
|
693
704
|
'domInteractive',
|
|
694
705
|
'domContentLoadedEventEnd',
|
|
695
706
|
'loadEventEnd',
|
|
696
|
-
)
|
|
707
|
+
)
|
|
697
708
|
|
|
698
|
-
return this._waitForAction()
|
|
709
|
+
return this._waitForAction()
|
|
699
710
|
}
|
|
700
711
|
|
|
701
712
|
/**
|
|
@@ -709,11 +720,11 @@ class Puppeteer extends Helper {
|
|
|
709
720
|
*/
|
|
710
721
|
async resizeWindow(width, height) {
|
|
711
722
|
if (width === 'maximize') {
|
|
712
|
-
throw new Error(
|
|
723
|
+
throw new Error("Puppeteer can't control windows, so it can't maximize it")
|
|
713
724
|
}
|
|
714
725
|
|
|
715
|
-
await this.page.setViewport({ width, height })
|
|
716
|
-
return this._waitForAction()
|
|
726
|
+
await this.page.setViewport({ width, height })
|
|
727
|
+
return this._waitForAction()
|
|
717
728
|
}
|
|
718
729
|
|
|
719
730
|
/**
|
|
@@ -729,9 +740,9 @@ class Puppeteer extends Helper {
|
|
|
729
740
|
*/
|
|
730
741
|
async setPuppeteerRequestHeaders(customHeaders) {
|
|
731
742
|
if (!customHeaders) {
|
|
732
|
-
throw new Error('Cannot send empty headers.')
|
|
743
|
+
throw new Error('Cannot send empty headers.')
|
|
733
744
|
}
|
|
734
|
-
return this.page.setExtraHTTPHeaders(customHeaders)
|
|
745
|
+
return this.page.setExtraHTTPHeaders(customHeaders)
|
|
735
746
|
}
|
|
736
747
|
|
|
737
748
|
/**
|
|
@@ -739,13 +750,13 @@ class Puppeteer extends Helper {
|
|
|
739
750
|
* {{ react }}
|
|
740
751
|
*/
|
|
741
752
|
async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
|
|
742
|
-
const els = await this._locate(locator)
|
|
743
|
-
assertElementExists(els, locator)
|
|
753
|
+
const els = await this._locate(locator)
|
|
754
|
+
assertElementExists(els, locator)
|
|
744
755
|
|
|
745
756
|
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
|
|
746
|
-
const { x, y } = await getClickablePoint(els[0])
|
|
747
|
-
await this.page.mouse.move(x + offsetX, y + offsetY)
|
|
748
|
-
return this._waitForAction()
|
|
757
|
+
const { x, y } = await getClickablePoint(els[0])
|
|
758
|
+
await this.page.mouse.move(x + offsetX, y + offsetY)
|
|
759
|
+
return this._waitForAction()
|
|
749
760
|
}
|
|
750
761
|
|
|
751
762
|
/**
|
|
@@ -753,13 +764,13 @@ class Puppeteer extends Helper {
|
|
|
753
764
|
*
|
|
754
765
|
*/
|
|
755
766
|
async focus(locator) {
|
|
756
|
-
const els = await this._locate(locator)
|
|
757
|
-
assertElementExists(els, locator, 'Element to focus')
|
|
758
|
-
const el = els[0]
|
|
767
|
+
const els = await this._locate(locator)
|
|
768
|
+
assertElementExists(els, locator, 'Element to focus')
|
|
769
|
+
const el = els[0]
|
|
759
770
|
|
|
760
|
-
await el.click()
|
|
761
|
-
await el.focus()
|
|
762
|
-
return this._waitForAction()
|
|
771
|
+
await el.click()
|
|
772
|
+
await el.focus()
|
|
773
|
+
return this._waitForAction()
|
|
763
774
|
}
|
|
764
775
|
|
|
765
776
|
/**
|
|
@@ -767,25 +778,25 @@ class Puppeteer extends Helper {
|
|
|
767
778
|
*
|
|
768
779
|
*/
|
|
769
780
|
async blur(locator) {
|
|
770
|
-
const els = await this._locate(locator)
|
|
771
|
-
assertElementExists(els, locator, 'Element to blur')
|
|
781
|
+
const els = await this._locate(locator)
|
|
782
|
+
assertElementExists(els, locator, 'Element to blur')
|
|
772
783
|
|
|
773
|
-
await blurElement(els[0], this.page)
|
|
774
|
-
return this._waitForAction()
|
|
784
|
+
await blurElement(els[0], this.page)
|
|
785
|
+
return this._waitForAction()
|
|
775
786
|
}
|
|
776
787
|
|
|
777
788
|
/**
|
|
778
789
|
* {{> dragAndDrop }}
|
|
779
790
|
*/
|
|
780
791
|
async dragAndDrop(srcElement, destElement) {
|
|
781
|
-
return proceedDragAndDrop.call(this, srcElement, destElement)
|
|
792
|
+
return proceedDragAndDrop.call(this, srcElement, destElement)
|
|
782
793
|
}
|
|
783
794
|
|
|
784
795
|
/**
|
|
785
796
|
* {{> refreshPage }}
|
|
786
797
|
*/
|
|
787
798
|
async refreshPage() {
|
|
788
|
-
return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation })
|
|
799
|
+
return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation })
|
|
789
800
|
}
|
|
790
801
|
|
|
791
802
|
/**
|
|
@@ -793,8 +804,8 @@ class Puppeteer extends Helper {
|
|
|
793
804
|
*/
|
|
794
805
|
scrollPageToTop() {
|
|
795
806
|
return this.executeScript(() => {
|
|
796
|
-
window.scrollTo(0, 0)
|
|
797
|
-
})
|
|
807
|
+
window.scrollTo(0, 0)
|
|
808
|
+
})
|
|
798
809
|
}
|
|
799
810
|
|
|
800
811
|
/**
|
|
@@ -802,16 +813,13 @@ class Puppeteer extends Helper {
|
|
|
802
813
|
*/
|
|
803
814
|
scrollPageToBottom() {
|
|
804
815
|
return this.executeScript(() => {
|
|
805
|
-
const body = document.body
|
|
806
|
-
const html = document.documentElement
|
|
807
|
-
window.scrollTo(
|
|
808
|
-
|
|
809
|
-
body.offsetHeight,
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
html.offsetHeight,
|
|
813
|
-
));
|
|
814
|
-
});
|
|
816
|
+
const body = document.body
|
|
817
|
+
const html = document.documentElement
|
|
818
|
+
window.scrollTo(
|
|
819
|
+
0,
|
|
820
|
+
Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
|
|
821
|
+
)
|
|
822
|
+
})
|
|
815
823
|
}
|
|
816
824
|
|
|
817
825
|
/**
|
|
@@ -819,68 +827,71 @@ class Puppeteer extends Helper {
|
|
|
819
827
|
*/
|
|
820
828
|
async scrollTo(locator, offsetX = 0, offsetY = 0) {
|
|
821
829
|
if (typeof locator === 'number' && typeof offsetX === 'number') {
|
|
822
|
-
offsetY = offsetX
|
|
823
|
-
offsetX = locator
|
|
824
|
-
locator = null
|
|
830
|
+
offsetY = offsetX
|
|
831
|
+
offsetX = locator
|
|
832
|
+
locator = null
|
|
825
833
|
}
|
|
826
834
|
|
|
827
835
|
if (locator) {
|
|
828
|
-
const els = await this._locate(locator)
|
|
829
|
-
assertElementExists(els, locator, 'Element')
|
|
830
|
-
const el = els[0]
|
|
831
|
-
await el.evaluate((el) => el.scrollIntoView())
|
|
832
|
-
const elementCoordinates = await getClickablePoint(els[0])
|
|
833
|
-
await this.executeScript(
|
|
836
|
+
const els = await this._locate(locator)
|
|
837
|
+
assertElementExists(els, locator, 'Element')
|
|
838
|
+
const el = els[0]
|
|
839
|
+
await el.evaluate((el) => el.scrollIntoView())
|
|
840
|
+
const elementCoordinates = await getClickablePoint(els[0])
|
|
841
|
+
await this.executeScript(
|
|
842
|
+
(x, y) => window.scrollBy(x, y),
|
|
843
|
+
elementCoordinates.x + offsetX,
|
|
844
|
+
elementCoordinates.y + offsetY,
|
|
845
|
+
)
|
|
834
846
|
} else {
|
|
835
|
-
await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY)
|
|
847
|
+
await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY)
|
|
836
848
|
}
|
|
837
|
-
return this._waitForAction()
|
|
849
|
+
return this._waitForAction()
|
|
838
850
|
}
|
|
839
851
|
|
|
840
852
|
/**
|
|
841
853
|
* {{> seeInTitle }}
|
|
842
854
|
*/
|
|
843
855
|
async seeInTitle(text) {
|
|
844
|
-
const title = await this.page.title()
|
|
845
|
-
stringIncludes('web page title').assert(text, title)
|
|
856
|
+
const title = await this.page.title()
|
|
857
|
+
stringIncludes('web page title').assert(text, title)
|
|
846
858
|
}
|
|
847
859
|
|
|
848
860
|
/**
|
|
849
861
|
* {{> grabPageScrollPosition }}
|
|
850
862
|
*/
|
|
851
863
|
async grabPageScrollPosition() {
|
|
852
|
-
/* eslint-disable comma-dangle */
|
|
853
864
|
function getScrollPosition() {
|
|
854
865
|
return {
|
|
855
866
|
x: window.pageXOffset,
|
|
856
|
-
y: window.pageYOffset
|
|
857
|
-
}
|
|
867
|
+
y: window.pageYOffset,
|
|
868
|
+
}
|
|
858
869
|
}
|
|
859
|
-
|
|
860
|
-
return this.executeScript(getScrollPosition)
|
|
870
|
+
|
|
871
|
+
return this.executeScript(getScrollPosition)
|
|
861
872
|
}
|
|
862
873
|
|
|
863
874
|
/**
|
|
864
875
|
* {{> seeTitleEquals }}
|
|
865
876
|
*/
|
|
866
877
|
async seeTitleEquals(text) {
|
|
867
|
-
const title = await this.page.title()
|
|
868
|
-
return equals('web page title').assert(title, text)
|
|
878
|
+
const title = await this.page.title()
|
|
879
|
+
return equals('web page title').assert(title, text)
|
|
869
880
|
}
|
|
870
881
|
|
|
871
882
|
/**
|
|
872
883
|
* {{> dontSeeInTitle }}
|
|
873
884
|
*/
|
|
874
885
|
async dontSeeInTitle(text) {
|
|
875
|
-
const title = await this.page.title()
|
|
876
|
-
stringIncludes('web page title').negate(text, title)
|
|
886
|
+
const title = await this.page.title()
|
|
887
|
+
stringIncludes('web page title').negate(text, title)
|
|
877
888
|
}
|
|
878
889
|
|
|
879
890
|
/**
|
|
880
891
|
* {{> grabTitle }}
|
|
881
892
|
*/
|
|
882
893
|
async grabTitle() {
|
|
883
|
-
return this.page.title()
|
|
894
|
+
return this.page.title()
|
|
884
895
|
}
|
|
885
896
|
|
|
886
897
|
/**
|
|
@@ -894,8 +905,8 @@ class Puppeteer extends Helper {
|
|
|
894
905
|
* {{ react }}
|
|
895
906
|
*/
|
|
896
907
|
async _locate(locator) {
|
|
897
|
-
const context = await this.context
|
|
898
|
-
return findElements.call(this, context, locator)
|
|
908
|
+
const context = await this.context
|
|
909
|
+
return findElements.call(this, context, locator)
|
|
899
910
|
}
|
|
900
911
|
|
|
901
912
|
/**
|
|
@@ -907,10 +918,10 @@ class Puppeteer extends Helper {
|
|
|
907
918
|
* ```
|
|
908
919
|
*/
|
|
909
920
|
async _locateCheckable(locator, providedContext = null) {
|
|
910
|
-
const context = providedContext || (await this._getContext())
|
|
911
|
-
const els = await findCheckable.call(this, locator, context)
|
|
912
|
-
assertElementExists(els[0], locator, 'Checkbox or radio')
|
|
913
|
-
return els[0]
|
|
921
|
+
const context = providedContext || (await this._getContext())
|
|
922
|
+
const els = await findCheckable.call(this, locator, context)
|
|
923
|
+
assertElementExists(els[0], locator, 'Checkbox or radio')
|
|
924
|
+
return els[0]
|
|
914
925
|
}
|
|
915
926
|
|
|
916
927
|
/**
|
|
@@ -921,8 +932,8 @@ class Puppeteer extends Helper {
|
|
|
921
932
|
* ```
|
|
922
933
|
*/
|
|
923
934
|
async _locateClickable(locator) {
|
|
924
|
-
const context = await this.context
|
|
925
|
-
return findClickable.call(this, context, locator)
|
|
935
|
+
const context = await this.context
|
|
936
|
+
return findClickable.call(this, context, locator)
|
|
926
937
|
}
|
|
927
938
|
|
|
928
939
|
/**
|
|
@@ -933,7 +944,7 @@ class Puppeteer extends Helper {
|
|
|
933
944
|
* ```
|
|
934
945
|
*/
|
|
935
946
|
async _locateFields(locator) {
|
|
936
|
-
return findFields.call(this, locator)
|
|
947
|
+
return findFields.call(this, locator)
|
|
937
948
|
}
|
|
938
949
|
|
|
939
950
|
/**
|
|
@@ -941,7 +952,7 @@ class Puppeteer extends Helper {
|
|
|
941
952
|
*
|
|
942
953
|
*/
|
|
943
954
|
async grabWebElements(locator) {
|
|
944
|
-
return this._locate(locator)
|
|
955
|
+
return this._locate(locator)
|
|
945
956
|
}
|
|
946
957
|
|
|
947
958
|
/**
|
|
@@ -955,17 +966,17 @@ class Puppeteer extends Helper {
|
|
|
955
966
|
* @param {number} [num=1]
|
|
956
967
|
*/
|
|
957
968
|
async switchToNextTab(num = 1) {
|
|
958
|
-
const pages = await this.browser.pages()
|
|
959
|
-
const index = pages.indexOf(this.page)
|
|
960
|
-
this.withinLocator = null
|
|
961
|
-
const page = pages[index + num]
|
|
969
|
+
const pages = await this.browser.pages()
|
|
970
|
+
const index = pages.indexOf(this.page)
|
|
971
|
+
this.withinLocator = null
|
|
972
|
+
const page = pages[index + num]
|
|
962
973
|
|
|
963
974
|
if (!page) {
|
|
964
|
-
throw new Error(`There is no ability to switch to next tab with offset ${num}`)
|
|
975
|
+
throw new Error(`There is no ability to switch to next tab with offset ${num}`)
|
|
965
976
|
}
|
|
966
977
|
|
|
967
|
-
await this._setPage(page)
|
|
968
|
-
return this._waitForAction()
|
|
978
|
+
await this._setPage(page)
|
|
979
|
+
return this._waitForAction()
|
|
969
980
|
}
|
|
970
981
|
|
|
971
982
|
/**
|
|
@@ -978,17 +989,17 @@ class Puppeteer extends Helper {
|
|
|
978
989
|
* @param {number} [num=1]
|
|
979
990
|
*/
|
|
980
991
|
async switchToPreviousTab(num = 1) {
|
|
981
|
-
const pages = await this.browser.pages()
|
|
982
|
-
const index = pages.indexOf(this.page)
|
|
983
|
-
this.withinLocator = null
|
|
984
|
-
const page = pages[index - num]
|
|
992
|
+
const pages = await this.browser.pages()
|
|
993
|
+
const index = pages.indexOf(this.page)
|
|
994
|
+
this.withinLocator = null
|
|
995
|
+
const page = pages[index - num]
|
|
985
996
|
|
|
986
997
|
if (!page) {
|
|
987
|
-
throw new Error(`There is no ability to switch to previous tab with offset ${num}`)
|
|
998
|
+
throw new Error(`There is no ability to switch to previous tab with offset ${num}`)
|
|
988
999
|
}
|
|
989
1000
|
|
|
990
|
-
await this._setPage(page)
|
|
991
|
-
return this._waitForAction()
|
|
1001
|
+
await this._setPage(page)
|
|
1002
|
+
return this._waitForAction()
|
|
992
1003
|
}
|
|
993
1004
|
|
|
994
1005
|
/**
|
|
@@ -999,10 +1010,10 @@ class Puppeteer extends Helper {
|
|
|
999
1010
|
* ```
|
|
1000
1011
|
*/
|
|
1001
1012
|
async closeCurrentTab() {
|
|
1002
|
-
const oldPage = this.page
|
|
1003
|
-
await this.switchToPreviousTab()
|
|
1004
|
-
await oldPage.close()
|
|
1005
|
-
return this._waitForAction()
|
|
1013
|
+
const oldPage = this.page
|
|
1014
|
+
await this.switchToPreviousTab()
|
|
1015
|
+
await oldPage.close()
|
|
1016
|
+
return this._waitForAction()
|
|
1006
1017
|
}
|
|
1007
1018
|
|
|
1008
1019
|
/**
|
|
@@ -1013,15 +1024,15 @@ class Puppeteer extends Helper {
|
|
|
1013
1024
|
* ```
|
|
1014
1025
|
*/
|
|
1015
1026
|
async closeOtherTabs() {
|
|
1016
|
-
const pages = await this.browser.pages()
|
|
1017
|
-
const otherPages = pages.filter(page => page !== this.page)
|
|
1027
|
+
const pages = await this.browser.pages()
|
|
1028
|
+
const otherPages = pages.filter((page) => page !== this.page)
|
|
1018
1029
|
|
|
1019
|
-
let p = Promise.resolve()
|
|
1030
|
+
let p = Promise.resolve()
|
|
1020
1031
|
otherPages.forEach((page) => {
|
|
1021
|
-
p = p.then(() => page.close())
|
|
1022
|
-
})
|
|
1023
|
-
await p
|
|
1024
|
-
return this._waitForAction()
|
|
1032
|
+
p = p.then(() => page.close())
|
|
1033
|
+
})
|
|
1034
|
+
await p
|
|
1035
|
+
return this._waitForAction()
|
|
1025
1036
|
}
|
|
1026
1037
|
|
|
1027
1038
|
/**
|
|
@@ -1032,16 +1043,16 @@ class Puppeteer extends Helper {
|
|
|
1032
1043
|
* ```
|
|
1033
1044
|
*/
|
|
1034
1045
|
async openNewTab() {
|
|
1035
|
-
await this._setPage(await this.browser.newPage())
|
|
1036
|
-
return this._waitForAction()
|
|
1046
|
+
await this._setPage(await this.browser.newPage())
|
|
1047
|
+
return this._waitForAction()
|
|
1037
1048
|
}
|
|
1038
1049
|
|
|
1039
1050
|
/**
|
|
1040
1051
|
* {{> grabNumberOfOpenTabs }}
|
|
1041
1052
|
*/
|
|
1042
1053
|
async grabNumberOfOpenTabs() {
|
|
1043
|
-
const pages = await this.browser.pages()
|
|
1044
|
-
return pages.length
|
|
1054
|
+
const pages = await this.browser.pages()
|
|
1055
|
+
return pages.length
|
|
1045
1056
|
}
|
|
1046
1057
|
|
|
1047
1058
|
/**
|
|
@@ -1049,14 +1060,22 @@ class Puppeteer extends Helper {
|
|
|
1049
1060
|
* {{ react }}
|
|
1050
1061
|
*/
|
|
1051
1062
|
async seeElement(locator) {
|
|
1052
|
-
let els = await this._locate(locator)
|
|
1053
|
-
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
|
|
1063
|
+
let els = await this._locate(locator)
|
|
1064
|
+
els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
|
|
1054
1065
|
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1055
|
-
els = await Promise.all(
|
|
1066
|
+
els = await Promise.all(
|
|
1067
|
+
els.map(
|
|
1068
|
+
async (el) =>
|
|
1069
|
+
(await el.evaluate(
|
|
1070
|
+
(node) =>
|
|
1071
|
+
window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
|
|
1072
|
+
)) && el,
|
|
1073
|
+
),
|
|
1074
|
+
)
|
|
1056
1075
|
try {
|
|
1057
|
-
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
|
|
1076
|
+
return empty('visible elements').negate(els.filter((v) => v).fill('ELEMENT'))
|
|
1058
1077
|
} catch (e) {
|
|
1059
|
-
dontSeeElementError(locator)
|
|
1078
|
+
dontSeeElementError(locator)
|
|
1060
1079
|
}
|
|
1061
1080
|
}
|
|
1062
1081
|
|
|
@@ -1065,14 +1084,22 @@ class Puppeteer extends Helper {
|
|
|
1065
1084
|
* {{ react }}
|
|
1066
1085
|
*/
|
|
1067
1086
|
async dontSeeElement(locator) {
|
|
1068
|
-
let els = await this._locate(locator)
|
|
1069
|
-
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
|
|
1087
|
+
let els = await this._locate(locator)
|
|
1088
|
+
els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
|
|
1070
1089
|
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1071
|
-
els = await Promise.all(
|
|
1090
|
+
els = await Promise.all(
|
|
1091
|
+
els.map(
|
|
1092
|
+
async (el) =>
|
|
1093
|
+
(await el.evaluate(
|
|
1094
|
+
(node) =>
|
|
1095
|
+
window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
|
|
1096
|
+
)) && el,
|
|
1097
|
+
),
|
|
1098
|
+
)
|
|
1072
1099
|
try {
|
|
1073
|
-
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
|
|
1100
|
+
return empty('visible elements').assert(els.filter((v) => v).fill('ELEMENT'))
|
|
1074
1101
|
} catch (e) {
|
|
1075
|
-
seeElementError(locator)
|
|
1102
|
+
seeElementError(locator)
|
|
1076
1103
|
}
|
|
1077
1104
|
}
|
|
1078
1105
|
|
|
@@ -1080,11 +1107,11 @@ class Puppeteer extends Helper {
|
|
|
1080
1107
|
* {{> seeElementInDOM }}
|
|
1081
1108
|
*/
|
|
1082
1109
|
async seeElementInDOM(locator) {
|
|
1083
|
-
const els = await this._locate(locator)
|
|
1110
|
+
const els = await this._locate(locator)
|
|
1084
1111
|
try {
|
|
1085
|
-
return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'))
|
|
1112
|
+
return empty('elements on page').negate(els.filter((v) => v).fill('ELEMENT'))
|
|
1086
1113
|
} catch (e) {
|
|
1087
|
-
dontSeeElementInDOMError(locator)
|
|
1114
|
+
dontSeeElementInDOMError(locator)
|
|
1088
1115
|
}
|
|
1089
1116
|
}
|
|
1090
1117
|
|
|
@@ -1092,11 +1119,11 @@ class Puppeteer extends Helper {
|
|
|
1092
1119
|
* {{> dontSeeElementInDOM }}
|
|
1093
1120
|
*/
|
|
1094
1121
|
async dontSeeElementInDOM(locator) {
|
|
1095
|
-
const els = await this._locate(locator)
|
|
1122
|
+
const els = await this._locate(locator)
|
|
1096
1123
|
try {
|
|
1097
|
-
return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'))
|
|
1124
|
+
return empty('elements on a page').assert(els.filter((v) => v).fill('ELEMENT'))
|
|
1098
1125
|
} catch (e) {
|
|
1099
|
-
seeElementInDOMError(locator)
|
|
1126
|
+
seeElementInDOMError(locator)
|
|
1100
1127
|
}
|
|
1101
1128
|
}
|
|
1102
1129
|
|
|
@@ -1106,7 +1133,7 @@ class Puppeteer extends Helper {
|
|
|
1106
1133
|
* {{ react }}
|
|
1107
1134
|
*/
|
|
1108
1135
|
async click(locator, context = null) {
|
|
1109
|
-
return proceedClick.call(this, locator, context)
|
|
1136
|
+
return proceedClick.call(this, locator, context)
|
|
1110
1137
|
}
|
|
1111
1138
|
|
|
1112
1139
|
/**
|
|
@@ -1115,28 +1142,33 @@ class Puppeteer extends Helper {
|
|
|
1115
1142
|
* {{ react }}
|
|
1116
1143
|
*/
|
|
1117
1144
|
async forceClick(locator, context = null) {
|
|
1118
|
-
let matcher = await this.context
|
|
1145
|
+
let matcher = await this.context
|
|
1119
1146
|
if (context) {
|
|
1120
|
-
const els = await this._locate(context)
|
|
1121
|
-
assertElementExists(els, context)
|
|
1122
|
-
matcher = els[0]
|
|
1147
|
+
const els = await this._locate(context)
|
|
1148
|
+
assertElementExists(els, context)
|
|
1149
|
+
matcher = els[0]
|
|
1123
1150
|
}
|
|
1124
1151
|
|
|
1125
|
-
const els = await findClickable.call(this, matcher, locator)
|
|
1152
|
+
const els = await findClickable.call(this, matcher, locator)
|
|
1126
1153
|
if (context) {
|
|
1127
|
-
assertElementExists(
|
|
1154
|
+
assertElementExists(
|
|
1155
|
+
els,
|
|
1156
|
+
locator,
|
|
1157
|
+
'Clickable element',
|
|
1158
|
+
`was not found inside element ${new Locator(context).toString()}`,
|
|
1159
|
+
)
|
|
1128
1160
|
} else {
|
|
1129
|
-
assertElementExists(els, locator, 'Clickable element')
|
|
1161
|
+
assertElementExists(els, locator, 'Clickable element')
|
|
1130
1162
|
}
|
|
1131
|
-
const elem = els[0]
|
|
1163
|
+
const elem = els[0]
|
|
1132
1164
|
return this.executeScript((el) => {
|
|
1133
1165
|
if (document.activeElement instanceof HTMLElement) {
|
|
1134
|
-
document.activeElement.blur()
|
|
1166
|
+
document.activeElement.blur()
|
|
1135
1167
|
}
|
|
1136
|
-
const event = document.createEvent('MouseEvent')
|
|
1137
|
-
event.initEvent('click', true, true)
|
|
1138
|
-
return el.dispatchEvent(event)
|
|
1139
|
-
}, elem)
|
|
1168
|
+
const event = document.createEvent('MouseEvent')
|
|
1169
|
+
event.initEvent('click', true, true)
|
|
1170
|
+
return el.dispatchEvent(event)
|
|
1171
|
+
}, elem)
|
|
1140
1172
|
}
|
|
1141
1173
|
|
|
1142
1174
|
/**
|
|
@@ -1145,7 +1177,7 @@ class Puppeteer extends Helper {
|
|
|
1145
1177
|
* {{ react }}
|
|
1146
1178
|
*/
|
|
1147
1179
|
async clickLink(locator, context = null) {
|
|
1148
|
-
return proceedClick.call(this, locator, context, { waitForNavigation: true })
|
|
1180
|
+
return proceedClick.call(this, locator, context, { waitForNavigation: true })
|
|
1149
1181
|
}
|
|
1150
1182
|
|
|
1151
1183
|
/**
|
|
@@ -1166,16 +1198,16 @@ class Puppeteer extends Helper {
|
|
|
1166
1198
|
* @param {string} [downloadPath='downloads'] change this parameter to set another directory for saving
|
|
1167
1199
|
*/
|
|
1168
1200
|
async handleDownloads(downloadPath = 'downloads') {
|
|
1169
|
-
downloadPath = path.join(global.output_dir, downloadPath)
|
|
1201
|
+
downloadPath = path.join(global.output_dir, downloadPath)
|
|
1170
1202
|
if (!fs.existsSync(downloadPath)) {
|
|
1171
|
-
fs.mkdirSync(downloadPath, '0777')
|
|
1203
|
+
fs.mkdirSync(downloadPath, '0777')
|
|
1172
1204
|
}
|
|
1173
|
-
fsExtra.emptyDirSync(downloadPath)
|
|
1205
|
+
fsExtra.emptyDirSync(downloadPath)
|
|
1174
1206
|
|
|
1175
1207
|
try {
|
|
1176
|
-
return this.page._client.send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath })
|
|
1208
|
+
return this.page._client.send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath })
|
|
1177
1209
|
} catch (e) {
|
|
1178
|
-
return this.page._client().send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath })
|
|
1210
|
+
return this.page._client().send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath })
|
|
1179
1211
|
}
|
|
1180
1212
|
}
|
|
1181
1213
|
|
|
@@ -1185,27 +1217,27 @@ class Puppeteer extends Helper {
|
|
|
1185
1217
|
* Please use `handleDownloads()` instead.
|
|
1186
1218
|
*/
|
|
1187
1219
|
async downloadFile(locator, customName) {
|
|
1188
|
-
let fileName
|
|
1189
|
-
await this.page.setRequestInterception(true)
|
|
1220
|
+
let fileName
|
|
1221
|
+
await this.page.setRequestInterception(true)
|
|
1190
1222
|
|
|
1191
1223
|
const xRequest = await new Promise((resolve) => {
|
|
1192
1224
|
this.page.on('request', (request) => {
|
|
1193
|
-
console.log('rq', request, customName)
|
|
1194
|
-
const grabbedFileName = request.url().split('/')[request.url().split('/').length - 1]
|
|
1195
|
-
const fileExtension = request.url().split('/')[request.url().split('/').length - 1].split('.')[1]
|
|
1196
|
-
console.log('nm', customName, fileExtension)
|
|
1225
|
+
console.log('rq', request, customName)
|
|
1226
|
+
const grabbedFileName = request.url().split('/')[request.url().split('/').length - 1]
|
|
1227
|
+
const fileExtension = request.url().split('/')[request.url().split('/').length - 1].split('.')[1]
|
|
1228
|
+
console.log('nm', customName, fileExtension)
|
|
1197
1229
|
if (customName && path.extname(customName) !== fileExtension) {
|
|
1198
|
-
console.log('bypassing a request')
|
|
1199
|
-
request.continue()
|
|
1200
|
-
return
|
|
1230
|
+
console.log('bypassing a request')
|
|
1231
|
+
request.continue()
|
|
1232
|
+
return
|
|
1201
1233
|
}
|
|
1202
|
-
customName ? fileName = `${customName}.${fileExtension}` : fileName = grabbedFileName
|
|
1203
|
-
request.abort()
|
|
1204
|
-
resolve(request)
|
|
1205
|
-
})
|
|
1206
|
-
})
|
|
1234
|
+
customName ? (fileName = `${customName}.${fileExtension}`) : (fileName = grabbedFileName)
|
|
1235
|
+
request.abort()
|
|
1236
|
+
resolve(request)
|
|
1237
|
+
})
|
|
1238
|
+
})
|
|
1207
1239
|
|
|
1208
|
-
await this.click(locator)
|
|
1240
|
+
await this.click(locator)
|
|
1209
1241
|
|
|
1210
1242
|
const options = {
|
|
1211
1243
|
encoding: null,
|
|
@@ -1213,10 +1245,10 @@ class Puppeteer extends Helper {
|
|
|
1213
1245
|
uri: xRequest._url,
|
|
1214
1246
|
body: xRequest._postData,
|
|
1215
1247
|
headers: xRequest._headers,
|
|
1216
|
-
}
|
|
1248
|
+
}
|
|
1217
1249
|
|
|
1218
|
-
const cookies = await this.page.cookies()
|
|
1219
|
-
options.headers.Cookie = cookies.map(ck => `${ck.name}=${ck.value}`).join(';')
|
|
1250
|
+
const cookies = await this.page.cookies()
|
|
1251
|
+
options.headers.Cookie = cookies.map((ck) => `${ck.name}=${ck.value}`).join(';')
|
|
1220
1252
|
|
|
1221
1253
|
const response = await axios({
|
|
1222
1254
|
method: options.method,
|
|
@@ -1224,24 +1256,26 @@ class Puppeteer extends Helper {
|
|
|
1224
1256
|
headers: options.headers,
|
|
1225
1257
|
responseType: 'arraybuffer',
|
|
1226
1258
|
onDownloadProgress(e) {
|
|
1227
|
-
console.log('+', e)
|
|
1259
|
+
console.log('+', e)
|
|
1228
1260
|
},
|
|
1229
|
-
})
|
|
1261
|
+
})
|
|
1230
1262
|
|
|
1231
|
-
const outputFile = path.join(`${global.output_dir}/${fileName}`)
|
|
1263
|
+
const outputFile = path.join(`${global.output_dir}/${fileName}`)
|
|
1232
1264
|
|
|
1233
1265
|
try {
|
|
1234
1266
|
await new Promise((resolve, reject) => {
|
|
1235
|
-
const wstream = fs.createWriteStream(outputFile)
|
|
1236
|
-
console.log(response)
|
|
1237
|
-
wstream.write(response.data)
|
|
1238
|
-
wstream.end()
|
|
1239
|
-
this.debug(`File is downloaded in ${outputFile}`)
|
|
1240
|
-
wstream.on('finish', () => {
|
|
1241
|
-
|
|
1242
|
-
|
|
1267
|
+
const wstream = fs.createWriteStream(outputFile)
|
|
1268
|
+
console.log(response)
|
|
1269
|
+
wstream.write(response.data)
|
|
1270
|
+
wstream.end()
|
|
1271
|
+
this.debug(`File is downloaded in ${outputFile}`)
|
|
1272
|
+
wstream.on('finish', () => {
|
|
1273
|
+
resolve(fileName)
|
|
1274
|
+
})
|
|
1275
|
+
wstream.on('error', reject)
|
|
1276
|
+
})
|
|
1243
1277
|
} catch (error) {
|
|
1244
|
-
throw new Error(`There is something wrong with downloaded file. ${error}`)
|
|
1278
|
+
throw new Error(`There is something wrong with downloaded file. ${error}`)
|
|
1245
1279
|
}
|
|
1246
1280
|
}
|
|
1247
1281
|
|
|
@@ -1251,7 +1285,7 @@ class Puppeteer extends Helper {
|
|
|
1251
1285
|
* {{ react }}
|
|
1252
1286
|
*/
|
|
1253
1287
|
async doubleClick(locator, context = null) {
|
|
1254
|
-
return proceedClick.call(this, locator, context, { clickCount: 2 })
|
|
1288
|
+
return proceedClick.call(this, locator, context, { clickCount: 2 })
|
|
1255
1289
|
}
|
|
1256
1290
|
|
|
1257
1291
|
/**
|
|
@@ -1260,20 +1294,19 @@ class Puppeteer extends Helper {
|
|
|
1260
1294
|
* {{ react }}
|
|
1261
1295
|
*/
|
|
1262
1296
|
async rightClick(locator, context = null) {
|
|
1263
|
-
return proceedClick.call(this, locator, context, { button: 'right' })
|
|
1297
|
+
return proceedClick.call(this, locator, context, { button: 'right' })
|
|
1264
1298
|
}
|
|
1265
1299
|
|
|
1266
1300
|
/**
|
|
1267
1301
|
* {{> checkOption }}
|
|
1268
1302
|
*/
|
|
1269
1303
|
async checkOption(field, context = null) {
|
|
1270
|
-
const elm = await this._locateCheckable(field, context)
|
|
1271
|
-
const curentlyChecked = await elm.getProperty('checked')
|
|
1272
|
-
.then(checkedProperty => checkedProperty.jsonValue());
|
|
1304
|
+
const elm = await this._locateCheckable(field, context)
|
|
1305
|
+
const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
|
|
1273
1306
|
// Only check if NOT currently checked
|
|
1274
1307
|
if (!curentlyChecked) {
|
|
1275
|
-
await elm.click()
|
|
1276
|
-
return this._waitForAction()
|
|
1308
|
+
await elm.click()
|
|
1309
|
+
return this._waitForAction()
|
|
1277
1310
|
}
|
|
1278
1311
|
}
|
|
1279
1312
|
|
|
@@ -1281,13 +1314,12 @@ class Puppeteer extends Helper {
|
|
|
1281
1314
|
* {{> uncheckOption }}
|
|
1282
1315
|
*/
|
|
1283
1316
|
async uncheckOption(field, context = null) {
|
|
1284
|
-
const elm = await this._locateCheckable(field, context)
|
|
1285
|
-
const curentlyChecked = await elm.getProperty('checked')
|
|
1286
|
-
.then(checkedProperty => checkedProperty.jsonValue());
|
|
1317
|
+
const elm = await this._locateCheckable(field, context)
|
|
1318
|
+
const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
|
|
1287
1319
|
// Only uncheck if currently checked
|
|
1288
1320
|
if (curentlyChecked) {
|
|
1289
|
-
await elm.click()
|
|
1290
|
-
return this._waitForAction()
|
|
1321
|
+
await elm.click()
|
|
1322
|
+
return this._waitForAction()
|
|
1291
1323
|
}
|
|
1292
1324
|
}
|
|
1293
1325
|
|
|
@@ -1295,32 +1327,32 @@ class Puppeteer extends Helper {
|
|
|
1295
1327
|
* {{> seeCheckboxIsChecked }}
|
|
1296
1328
|
*/
|
|
1297
1329
|
async seeCheckboxIsChecked(field) {
|
|
1298
|
-
return proceedIsChecked.call(this, 'assert', field)
|
|
1330
|
+
return proceedIsChecked.call(this, 'assert', field)
|
|
1299
1331
|
}
|
|
1300
1332
|
|
|
1301
1333
|
/**
|
|
1302
1334
|
* {{> dontSeeCheckboxIsChecked }}
|
|
1303
1335
|
*/
|
|
1304
1336
|
async dontSeeCheckboxIsChecked(field) {
|
|
1305
|
-
return proceedIsChecked.call(this, 'negate', field)
|
|
1337
|
+
return proceedIsChecked.call(this, 'negate', field)
|
|
1306
1338
|
}
|
|
1307
1339
|
|
|
1308
1340
|
/**
|
|
1309
1341
|
* {{> pressKeyDown }}
|
|
1310
1342
|
*/
|
|
1311
1343
|
async pressKeyDown(key) {
|
|
1312
|
-
key = getNormalizedKey.call(this, key)
|
|
1313
|
-
await this.page.keyboard.down(key)
|
|
1314
|
-
return this._waitForAction()
|
|
1344
|
+
key = getNormalizedKey.call(this, key)
|
|
1345
|
+
await this.page.keyboard.down(key)
|
|
1346
|
+
return this._waitForAction()
|
|
1315
1347
|
}
|
|
1316
1348
|
|
|
1317
1349
|
/**
|
|
1318
1350
|
* {{> pressKeyUp }}
|
|
1319
1351
|
*/
|
|
1320
1352
|
async pressKeyUp(key) {
|
|
1321
|
-
key = getNormalizedKey.call(this, key)
|
|
1322
|
-
await this.page.keyboard.up(key)
|
|
1323
|
-
return this._waitForAction()
|
|
1353
|
+
key = getNormalizedKey.call(this, key)
|
|
1354
|
+
await this.page.keyboard.up(key)
|
|
1355
|
+
return this._waitForAction()
|
|
1324
1356
|
}
|
|
1325
1357
|
|
|
1326
1358
|
/**
|
|
@@ -1329,28 +1361,28 @@ class Puppeteer extends Helper {
|
|
|
1329
1361
|
* {{> pressKeyWithKeyNormalization }}
|
|
1330
1362
|
*/
|
|
1331
1363
|
async pressKey(key) {
|
|
1332
|
-
const modifiers = []
|
|
1364
|
+
const modifiers = []
|
|
1333
1365
|
if (Array.isArray(key)) {
|
|
1334
1366
|
for (let k of key) {
|
|
1335
|
-
k = getNormalizedKey.call(this, k)
|
|
1367
|
+
k = getNormalizedKey.call(this, k)
|
|
1336
1368
|
if (isModifierKey(k)) {
|
|
1337
|
-
modifiers.push(k)
|
|
1369
|
+
modifiers.push(k)
|
|
1338
1370
|
} else {
|
|
1339
|
-
key = k
|
|
1340
|
-
break
|
|
1371
|
+
key = k
|
|
1372
|
+
break
|
|
1341
1373
|
}
|
|
1342
1374
|
}
|
|
1343
1375
|
} else {
|
|
1344
|
-
key = getNormalizedKey.call(this, key)
|
|
1376
|
+
key = getNormalizedKey.call(this, key)
|
|
1345
1377
|
}
|
|
1346
1378
|
for (const modifier of modifiers) {
|
|
1347
|
-
await this.page.keyboard.down(modifier)
|
|
1379
|
+
await this.page.keyboard.down(modifier)
|
|
1348
1380
|
}
|
|
1349
|
-
await this.page.keyboard.press(key)
|
|
1381
|
+
await this.page.keyboard.press(key)
|
|
1350
1382
|
for (const modifier of modifiers) {
|
|
1351
|
-
await this.page.keyboard.up(modifier)
|
|
1383
|
+
await this.page.keyboard.up(modifier)
|
|
1352
1384
|
}
|
|
1353
|
-
return this._waitForAction()
|
|
1385
|
+
return this._waitForAction()
|
|
1354
1386
|
}
|
|
1355
1387
|
|
|
1356
1388
|
/**
|
|
@@ -1358,13 +1390,13 @@ class Puppeteer extends Helper {
|
|
|
1358
1390
|
*/
|
|
1359
1391
|
async type(keys, delay = null) {
|
|
1360
1392
|
if (!Array.isArray(keys)) {
|
|
1361
|
-
keys = keys.toString()
|
|
1362
|
-
keys = keys.split('')
|
|
1393
|
+
keys = keys.toString()
|
|
1394
|
+
keys = keys.split('')
|
|
1363
1395
|
}
|
|
1364
1396
|
|
|
1365
1397
|
for (const key of keys) {
|
|
1366
|
-
await this.page.keyboard.press(key)
|
|
1367
|
-
if (delay) await this.wait(delay / 1000)
|
|
1398
|
+
await this.page.keyboard.press(key)
|
|
1399
|
+
if (delay) await this.wait(delay / 1000)
|
|
1368
1400
|
}
|
|
1369
1401
|
}
|
|
1370
1402
|
|
|
@@ -1373,28 +1405,28 @@ class Puppeteer extends Helper {
|
|
|
1373
1405
|
* {{ react }}
|
|
1374
1406
|
*/
|
|
1375
1407
|
async fillField(field, value) {
|
|
1376
|
-
const els = await findVisibleFields.call(this, field)
|
|
1377
|
-
assertElementExists(els, field, 'Field')
|
|
1378
|
-
const el = els[0]
|
|
1379
|
-
const tag = await el.getProperty('tagName').then(el => el.jsonValue())
|
|
1380
|
-
const editable = await el.getProperty('contenteditable').then(el => el.jsonValue())
|
|
1408
|
+
const els = await findVisibleFields.call(this, field)
|
|
1409
|
+
assertElementExists(els, field, 'Field')
|
|
1410
|
+
const el = els[0]
|
|
1411
|
+
const tag = await el.getProperty('tagName').then((el) => el.jsonValue())
|
|
1412
|
+
const editable = await el.getProperty('contenteditable').then((el) => el.jsonValue())
|
|
1381
1413
|
if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
1382
|
-
await this._evaluateHandeInContext(el => el.value = '', el)
|
|
1414
|
+
await this._evaluateHandeInContext((el) => (el.value = ''), el)
|
|
1383
1415
|
} else if (editable) {
|
|
1384
|
-
await this._evaluateHandeInContext(el => el.innerHTML = '', el)
|
|
1416
|
+
await this._evaluateHandeInContext((el) => (el.innerHTML = ''), el)
|
|
1385
1417
|
}
|
|
1386
1418
|
|
|
1387
|
-
highlightActiveElement.call(this, el, await this._getContext())
|
|
1388
|
-
await el.type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
1419
|
+
highlightActiveElement.call(this, el, await this._getContext())
|
|
1420
|
+
await el.type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
1389
1421
|
|
|
1390
|
-
return this._waitForAction()
|
|
1422
|
+
return this._waitForAction()
|
|
1391
1423
|
}
|
|
1392
1424
|
|
|
1393
1425
|
/**
|
|
1394
1426
|
* {{> clearField }}
|
|
1395
1427
|
*/
|
|
1396
1428
|
async clearField(field) {
|
|
1397
|
-
return this.fillField(field, '')
|
|
1429
|
+
return this.fillField(field, '')
|
|
1398
1430
|
}
|
|
1399
1431
|
|
|
1400
1432
|
/**
|
|
@@ -1403,28 +1435,28 @@ class Puppeteer extends Helper {
|
|
|
1403
1435
|
* {{ react }}
|
|
1404
1436
|
*/
|
|
1405
1437
|
async appendField(field, value) {
|
|
1406
|
-
const els = await findVisibleFields.call(this, field)
|
|
1407
|
-
assertElementExists(els, field, 'Field')
|
|
1408
|
-
highlightActiveElement.call(this, els[0], await this._getContext())
|
|
1409
|
-
await els[0].press('End')
|
|
1410
|
-
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
1411
|
-
return this._waitForAction()
|
|
1438
|
+
const els = await findVisibleFields.call(this, field)
|
|
1439
|
+
assertElementExists(els, field, 'Field')
|
|
1440
|
+
highlightActiveElement.call(this, els[0], await this._getContext())
|
|
1441
|
+
await els[0].press('End')
|
|
1442
|
+
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
1443
|
+
return this._waitForAction()
|
|
1412
1444
|
}
|
|
1413
1445
|
|
|
1414
1446
|
/**
|
|
1415
1447
|
* {{> seeInField }}
|
|
1416
1448
|
*/
|
|
1417
1449
|
async seeInField(field, value) {
|
|
1418
|
-
const _value =
|
|
1419
|
-
return proceedSeeInField.call(this, 'assert', field, _value)
|
|
1450
|
+
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1451
|
+
return proceedSeeInField.call(this, 'assert', field, _value)
|
|
1420
1452
|
}
|
|
1421
1453
|
|
|
1422
1454
|
/**
|
|
1423
1455
|
* {{> dontSeeInField }}
|
|
1424
1456
|
*/
|
|
1425
1457
|
async dontSeeInField(field, value) {
|
|
1426
|
-
const _value =
|
|
1427
|
-
return proceedSeeInField.call(this, 'negate', field, _value)
|
|
1458
|
+
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1459
|
+
return proceedSeeInField.call(this, 'negate', field, _value)
|
|
1428
1460
|
}
|
|
1429
1461
|
|
|
1430
1462
|
/**
|
|
@@ -1433,48 +1465,48 @@ class Puppeteer extends Helper {
|
|
|
1433
1465
|
* {{> attachFile }}
|
|
1434
1466
|
*/
|
|
1435
1467
|
async attachFile(locator, pathToFile) {
|
|
1436
|
-
const file = path.join(global.codecept_dir, pathToFile)
|
|
1468
|
+
const file = path.join(global.codecept_dir, pathToFile)
|
|
1437
1469
|
|
|
1438
1470
|
if (!fileExists(file)) {
|
|
1439
|
-
throw new Error(`File at ${file} can not be found on local system`)
|
|
1471
|
+
throw new Error(`File at ${file} can not be found on local system`)
|
|
1440
1472
|
}
|
|
1441
|
-
const els = await findFields.call(this, locator)
|
|
1442
|
-
assertElementExists(els, locator, 'Field')
|
|
1443
|
-
await els[0].uploadFile(file)
|
|
1444
|
-
return this._waitForAction()
|
|
1473
|
+
const els = await findFields.call(this, locator)
|
|
1474
|
+
assertElementExists(els, locator, 'Field')
|
|
1475
|
+
await els[0].uploadFile(file)
|
|
1476
|
+
return this._waitForAction()
|
|
1445
1477
|
}
|
|
1446
1478
|
|
|
1447
1479
|
/**
|
|
1448
1480
|
* {{> selectOption }}
|
|
1449
1481
|
*/
|
|
1450
1482
|
async selectOption(select, option) {
|
|
1451
|
-
const els = await findVisibleFields.call(this, select)
|
|
1452
|
-
assertElementExists(els, select, 'Selectable field')
|
|
1453
|
-
const el = els[0]
|
|
1454
|
-
if ((await el.getProperty('tagName').then(t => t.jsonValue())) !== 'SELECT') {
|
|
1455
|
-
throw new Error('Element is not <select>')
|
|
1483
|
+
const els = await findVisibleFields.call(this, select)
|
|
1484
|
+
assertElementExists(els, select, 'Selectable field')
|
|
1485
|
+
const el = els[0]
|
|
1486
|
+
if ((await el.getProperty('tagName').then((t) => t.jsonValue())) !== 'SELECT') {
|
|
1487
|
+
throw new Error('Element is not <select>')
|
|
1456
1488
|
}
|
|
1457
|
-
highlightActiveElement.call(this, els[0], await this._getContext())
|
|
1458
|
-
if (!Array.isArray(option)) option = [option]
|
|
1489
|
+
highlightActiveElement.call(this, els[0], await this._getContext())
|
|
1490
|
+
if (!Array.isArray(option)) option = [option]
|
|
1459
1491
|
|
|
1460
1492
|
for (const key in option) {
|
|
1461
|
-
const opt = xpathLocator.literal(option[key])
|
|
1462
|
-
let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
|
|
1493
|
+
const opt = xpathLocator.literal(option[key])
|
|
1494
|
+
let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
|
|
1463
1495
|
if (optEl.length) {
|
|
1464
|
-
this._evaluateHandeInContext(el => el.selected = true, optEl[0])
|
|
1465
|
-
continue
|
|
1496
|
+
this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
|
|
1497
|
+
continue
|
|
1466
1498
|
}
|
|
1467
|
-
optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
|
|
1499
|
+
optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
|
|
1468
1500
|
if (optEl.length) {
|
|
1469
|
-
this._evaluateHandeInContext(el => el.selected = true, optEl[0])
|
|
1501
|
+
this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
|
|
1470
1502
|
}
|
|
1471
1503
|
}
|
|
1472
1504
|
await this._evaluateHandeInContext((element) => {
|
|
1473
|
-
element.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1474
|
-
element.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1475
|
-
}, el)
|
|
1505
|
+
element.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1506
|
+
element.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1507
|
+
}, el)
|
|
1476
1508
|
|
|
1477
|
-
return this._waitForAction()
|
|
1509
|
+
return this._waitForAction()
|
|
1478
1510
|
}
|
|
1479
1511
|
|
|
1480
1512
|
/**
|
|
@@ -1482,40 +1514,48 @@ class Puppeteer extends Helper {
|
|
|
1482
1514
|
* {{ react }}
|
|
1483
1515
|
*/
|
|
1484
1516
|
async grabNumberOfVisibleElements(locator) {
|
|
1485
|
-
let els = await this._locate(locator)
|
|
1486
|
-
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
|
|
1517
|
+
let els = await this._locate(locator)
|
|
1518
|
+
els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
|
|
1487
1519
|
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1488
|
-
els = await Promise.all(
|
|
1520
|
+
els = await Promise.all(
|
|
1521
|
+
els.map(
|
|
1522
|
+
async (el) =>
|
|
1523
|
+
(await el.evaluate(
|
|
1524
|
+
(node) =>
|
|
1525
|
+
window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
|
|
1526
|
+
)) && el,
|
|
1527
|
+
),
|
|
1528
|
+
)
|
|
1489
1529
|
|
|
1490
|
-
return els.filter(v => v).length
|
|
1530
|
+
return els.filter((v) => v).length
|
|
1491
1531
|
}
|
|
1492
1532
|
|
|
1493
1533
|
/**
|
|
1494
1534
|
* {{> seeInCurrentUrl }}
|
|
1495
1535
|
*/
|
|
1496
1536
|
async seeInCurrentUrl(url) {
|
|
1497
|
-
stringIncludes('url').assert(url, await this._getPageUrl())
|
|
1537
|
+
stringIncludes('url').assert(url, await this._getPageUrl())
|
|
1498
1538
|
}
|
|
1499
1539
|
|
|
1500
1540
|
/**
|
|
1501
1541
|
* {{> dontSeeInCurrentUrl }}
|
|
1502
1542
|
*/
|
|
1503
1543
|
async dontSeeInCurrentUrl(url) {
|
|
1504
|
-
stringIncludes('url').negate(url, await this._getPageUrl())
|
|
1544
|
+
stringIncludes('url').negate(url, await this._getPageUrl())
|
|
1505
1545
|
}
|
|
1506
1546
|
|
|
1507
1547
|
/**
|
|
1508
1548
|
* {{> seeCurrentUrlEquals }}
|
|
1509
1549
|
*/
|
|
1510
1550
|
async seeCurrentUrlEquals(url) {
|
|
1511
|
-
urlEquals(this.options.url).assert(url, await this._getPageUrl())
|
|
1551
|
+
urlEquals(this.options.url).assert(url, await this._getPageUrl())
|
|
1512
1552
|
}
|
|
1513
1553
|
|
|
1514
1554
|
/**
|
|
1515
1555
|
* {{> dontSeeCurrentUrlEquals }}
|
|
1516
1556
|
*/
|
|
1517
1557
|
async dontSeeCurrentUrlEquals(url) {
|
|
1518
|
-
urlEquals(this.options.url).negate(url, await this._getPageUrl())
|
|
1558
|
+
urlEquals(this.options.url).negate(url, await this._getPageUrl())
|
|
1519
1559
|
}
|
|
1520
1560
|
|
|
1521
1561
|
/**
|
|
@@ -1524,14 +1564,14 @@ class Puppeteer extends Helper {
|
|
|
1524
1564
|
* {{ react }}
|
|
1525
1565
|
*/
|
|
1526
1566
|
async see(text, context = null) {
|
|
1527
|
-
return proceedSee.call(this, 'assert', text, context)
|
|
1567
|
+
return proceedSee.call(this, 'assert', text, context)
|
|
1528
1568
|
}
|
|
1529
1569
|
|
|
1530
1570
|
/**
|
|
1531
1571
|
* {{> seeTextEquals }}
|
|
1532
1572
|
*/
|
|
1533
1573
|
async seeTextEquals(text, context = null) {
|
|
1534
|
-
return proceedSee.call(this, 'assert', text, context, true)
|
|
1574
|
+
return proceedSee.call(this, 'assert', text, context, true)
|
|
1535
1575
|
}
|
|
1536
1576
|
|
|
1537
1577
|
/**
|
|
@@ -1540,14 +1580,14 @@ class Puppeteer extends Helper {
|
|
|
1540
1580
|
* {{ react }}
|
|
1541
1581
|
*/
|
|
1542
1582
|
async dontSee(text, context = null) {
|
|
1543
|
-
return proceedSee.call(this, 'negate', text, context)
|
|
1583
|
+
return proceedSee.call(this, 'negate', text, context)
|
|
1544
1584
|
}
|
|
1545
1585
|
|
|
1546
1586
|
/**
|
|
1547
1587
|
* {{> grabSource }}
|
|
1548
1588
|
*/
|
|
1549
1589
|
async grabSource() {
|
|
1550
|
-
return this.page.content()
|
|
1590
|
+
return this.page.content()
|
|
1551
1591
|
}
|
|
1552
1592
|
|
|
1553
1593
|
/**
|
|
@@ -1560,32 +1600,32 @@ class Puppeteer extends Helper {
|
|
|
1560
1600
|
* @return {Promise<any[]>}
|
|
1561
1601
|
*/
|
|
1562
1602
|
async grabBrowserLogs() {
|
|
1563
|
-
const logs = consoleLogStore.entries
|
|
1564
|
-
consoleLogStore.clear()
|
|
1565
|
-
return logs
|
|
1603
|
+
const logs = consoleLogStore.entries
|
|
1604
|
+
consoleLogStore.clear()
|
|
1605
|
+
return logs
|
|
1566
1606
|
}
|
|
1567
1607
|
|
|
1568
1608
|
/**
|
|
1569
1609
|
* {{> grabCurrentUrl }}
|
|
1570
1610
|
*/
|
|
1571
1611
|
async grabCurrentUrl() {
|
|
1572
|
-
return this._getPageUrl()
|
|
1612
|
+
return this._getPageUrl()
|
|
1573
1613
|
}
|
|
1574
1614
|
|
|
1575
1615
|
/**
|
|
1576
1616
|
* {{> seeInSource }}
|
|
1577
1617
|
*/
|
|
1578
1618
|
async seeInSource(text) {
|
|
1579
|
-
const source = await this.page.content()
|
|
1580
|
-
stringIncludes('HTML source of a page').assert(text, source)
|
|
1619
|
+
const source = await this.page.content()
|
|
1620
|
+
stringIncludes('HTML source of a page').assert(text, source)
|
|
1581
1621
|
}
|
|
1582
1622
|
|
|
1583
1623
|
/**
|
|
1584
1624
|
* {{> dontSeeInSource }}
|
|
1585
1625
|
*/
|
|
1586
1626
|
async dontSeeInSource(text) {
|
|
1587
|
-
const source = await this.page.content()
|
|
1588
|
-
stringIncludes('HTML source of a page').negate(text, source)
|
|
1627
|
+
const source = await this.page.content()
|
|
1628
|
+
stringIncludes('HTML source of a page').negate(text, source)
|
|
1589
1629
|
}
|
|
1590
1630
|
|
|
1591
1631
|
/**
|
|
@@ -1594,8 +1634,10 @@ class Puppeteer extends Helper {
|
|
|
1594
1634
|
* {{ react }}
|
|
1595
1635
|
*/
|
|
1596
1636
|
async seeNumberOfElements(locator, num) {
|
|
1597
|
-
const elements = await this._locate(locator)
|
|
1598
|
-
return equals(
|
|
1637
|
+
const elements = await this._locate(locator)
|
|
1638
|
+
return equals(
|
|
1639
|
+
`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`,
|
|
1640
|
+
).assert(elements.length, num)
|
|
1599
1641
|
}
|
|
1600
1642
|
|
|
1601
1643
|
/**
|
|
@@ -1604,8 +1646,11 @@ class Puppeteer extends Helper {
|
|
|
1604
1646
|
* {{ react }}
|
|
1605
1647
|
*/
|
|
1606
1648
|
async seeNumberOfVisibleElements(locator, num) {
|
|
1607
|
-
const res = await this.grabNumberOfVisibleElements(locator)
|
|
1608
|
-
return equals(`expected number of visible elements (${
|
|
1649
|
+
const res = await this.grabNumberOfVisibleElements(locator)
|
|
1650
|
+
return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(
|
|
1651
|
+
res,
|
|
1652
|
+
num,
|
|
1653
|
+
)
|
|
1609
1654
|
}
|
|
1610
1655
|
|
|
1611
1656
|
/**
|
|
@@ -1613,9 +1658,9 @@ class Puppeteer extends Helper {
|
|
|
1613
1658
|
*/
|
|
1614
1659
|
async setCookie(cookie) {
|
|
1615
1660
|
if (Array.isArray(cookie)) {
|
|
1616
|
-
return this.page.setCookie(...cookie)
|
|
1661
|
+
return this.page.setCookie(...cookie)
|
|
1617
1662
|
}
|
|
1618
|
-
return this.page.setCookie(cookie)
|
|
1663
|
+
return this.page.setCookie(cookie)
|
|
1619
1664
|
}
|
|
1620
1665
|
|
|
1621
1666
|
/**
|
|
@@ -1623,16 +1668,16 @@ class Puppeteer extends Helper {
|
|
|
1623
1668
|
*
|
|
1624
1669
|
*/
|
|
1625
1670
|
async seeCookie(name) {
|
|
1626
|
-
const cookies = await this.page.cookies()
|
|
1627
|
-
empty(`cookie ${name} to be set`).negate(cookies.filter(c => c.name === name))
|
|
1671
|
+
const cookies = await this.page.cookies()
|
|
1672
|
+
empty(`cookie ${name} to be set`).negate(cookies.filter((c) => c.name === name))
|
|
1628
1673
|
}
|
|
1629
1674
|
|
|
1630
1675
|
/**
|
|
1631
1676
|
* {{> dontSeeCookie }}
|
|
1632
1677
|
*/
|
|
1633
1678
|
async dontSeeCookie(name) {
|
|
1634
|
-
const cookies = await this.page.cookies()
|
|
1635
|
-
empty(`cookie ${name} to be set`).assert(cookies.filter(c => c.name === name))
|
|
1679
|
+
const cookies = await this.page.cookies()
|
|
1680
|
+
empty(`cookie ${name} not to be set`).assert(cookies.filter((c) => c.name === name))
|
|
1636
1681
|
}
|
|
1637
1682
|
|
|
1638
1683
|
/**
|
|
@@ -1641,10 +1686,10 @@ class Puppeteer extends Helper {
|
|
|
1641
1686
|
* Returns cookie in JSON format. If name not passed returns all cookies for this domain.
|
|
1642
1687
|
*/
|
|
1643
1688
|
async grabCookie(name) {
|
|
1644
|
-
const cookies = await this.page.cookies()
|
|
1645
|
-
if (!name) return cookies
|
|
1646
|
-
const cookie = cookies.filter(c => c.name === name)
|
|
1647
|
-
if (cookie[0]) return cookie[0]
|
|
1689
|
+
const cookies = await this.page.cookies()
|
|
1690
|
+
if (!name) return cookies
|
|
1691
|
+
const cookie = cookies.filter((c) => c.name === name)
|
|
1692
|
+
if (cookie[0]) return cookie[0]
|
|
1648
1693
|
}
|
|
1649
1694
|
|
|
1650
1695
|
/**
|
|
@@ -1652,44 +1697,47 @@ class Puppeteer extends Helper {
|
|
|
1652
1697
|
*/
|
|
1653
1698
|
async waitForCookie(name, sec) {
|
|
1654
1699
|
// by default, we will retry 3 times
|
|
1655
|
-
let retries = 3
|
|
1656
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
1700
|
+
let retries = 3
|
|
1701
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
1657
1702
|
|
|
1658
1703
|
if (sec) {
|
|
1659
|
-
retries = sec
|
|
1704
|
+
retries = sec
|
|
1660
1705
|
} else {
|
|
1661
|
-
retries = Math.ceil(waitTimeout / 1000) - 1
|
|
1706
|
+
retries = Math.ceil(waitTimeout / 1000) - 1
|
|
1662
1707
|
}
|
|
1663
1708
|
|
|
1664
|
-
return promiseRetry(
|
|
1665
|
-
|
|
1666
|
-
const
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1709
|
+
return promiseRetry(
|
|
1710
|
+
async (retry, number) => {
|
|
1711
|
+
const _grabCookie = async (name) => {
|
|
1712
|
+
const cookies = await this.page.cookies()
|
|
1713
|
+
const cookie = cookies.filter((c) => c.name === name)
|
|
1714
|
+
if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
|
|
1715
|
+
}
|
|
1670
1716
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1717
|
+
this.debugSection('Wait for cookie: ', name)
|
|
1718
|
+
if (number > 1) this.debugSection('Retrying... Attempt #', number)
|
|
1673
1719
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1720
|
+
try {
|
|
1721
|
+
await _grabCookie(name)
|
|
1722
|
+
} catch (e) {
|
|
1723
|
+
retry(e)
|
|
1724
|
+
}
|
|
1725
|
+
},
|
|
1726
|
+
{ retries, maxTimeout: 1000 },
|
|
1727
|
+
)
|
|
1680
1728
|
}
|
|
1681
1729
|
|
|
1682
1730
|
/**
|
|
1683
1731
|
* {{> clearCookie }}
|
|
1684
1732
|
*/
|
|
1685
1733
|
async clearCookie(name) {
|
|
1686
|
-
const cookies = await this.page.cookies()
|
|
1734
|
+
const cookies = await this.page.cookies()
|
|
1687
1735
|
if (!name) {
|
|
1688
|
-
return this.page.deleteCookie.apply(this.page, cookies)
|
|
1736
|
+
return this.page.deleteCookie.apply(this.page, cookies)
|
|
1689
1737
|
}
|
|
1690
|
-
const cookie = cookies.filter(c => c.name === name)
|
|
1691
|
-
if (!cookie[0]) return
|
|
1692
|
-
return this.page.deleteCookie(cookie[0])
|
|
1738
|
+
const cookie = cookies.filter((c) => c.name === name)
|
|
1739
|
+
if (!cookie[0]) return
|
|
1740
|
+
return this.page.deleteCookie(cookie[0])
|
|
1693
1741
|
}
|
|
1694
1742
|
|
|
1695
1743
|
/**
|
|
@@ -1698,11 +1746,11 @@ class Puppeteer extends Helper {
|
|
|
1698
1746
|
* {{> executeScript }}
|
|
1699
1747
|
*/
|
|
1700
1748
|
async executeScript(...args) {
|
|
1701
|
-
let context = await this._getContext()
|
|
1749
|
+
let context = await this._getContext()
|
|
1702
1750
|
if (this.context && this.context.constructor.name === 'CdpFrame') {
|
|
1703
|
-
context = this.context
|
|
1751
|
+
context = this.context // switching to iframe context
|
|
1704
1752
|
}
|
|
1705
|
-
return context.evaluate.apply(context, args)
|
|
1753
|
+
return context.evaluate.apply(context, args)
|
|
1706
1754
|
}
|
|
1707
1755
|
|
|
1708
1756
|
/**
|
|
@@ -1711,16 +1759,16 @@ class Puppeteer extends Helper {
|
|
|
1711
1759
|
*/
|
|
1712
1760
|
async executeAsyncScript(...args) {
|
|
1713
1761
|
const asyncFn = function () {
|
|
1714
|
-
const args = Array.from(arguments)
|
|
1715
|
-
const fn = eval(`(${args.shift()})`)
|
|
1762
|
+
const args = Array.from(arguments)
|
|
1763
|
+
const fn = eval(`(${args.shift()})`) // eslint-disable-line no-eval
|
|
1716
1764
|
return new Promise((done) => {
|
|
1717
|
-
args.push(done)
|
|
1718
|
-
fn.apply(null, args)
|
|
1719
|
-
})
|
|
1720
|
-
}
|
|
1721
|
-
args[0] = args[0].toString()
|
|
1722
|
-
args.unshift(asyncFn)
|
|
1723
|
-
return this.page.evaluate.apply(this.page, args)
|
|
1765
|
+
args.push(done)
|
|
1766
|
+
fn.apply(null, args)
|
|
1767
|
+
})
|
|
1768
|
+
}
|
|
1769
|
+
args[0] = args[0].toString()
|
|
1770
|
+
args.unshift(asyncFn)
|
|
1771
|
+
return this.page.evaluate.apply(this.page, args)
|
|
1724
1772
|
}
|
|
1725
1773
|
|
|
1726
1774
|
/**
|
|
@@ -1728,12 +1776,12 @@ class Puppeteer extends Helper {
|
|
|
1728
1776
|
* {{ react }}
|
|
1729
1777
|
*/
|
|
1730
1778
|
async grabTextFromAll(locator) {
|
|
1731
|
-
const els = await this._locate(locator)
|
|
1732
|
-
const texts = []
|
|
1779
|
+
const els = await this._locate(locator)
|
|
1780
|
+
const texts = []
|
|
1733
1781
|
for (const el of els) {
|
|
1734
|
-
texts.push(await (await el.getProperty('innerText')).jsonValue())
|
|
1782
|
+
texts.push(await (await el.getProperty('innerText')).jsonValue())
|
|
1735
1783
|
}
|
|
1736
|
-
return texts
|
|
1784
|
+
return texts
|
|
1737
1785
|
}
|
|
1738
1786
|
|
|
1739
1787
|
/**
|
|
@@ -1741,60 +1789,60 @@ class Puppeteer extends Helper {
|
|
|
1741
1789
|
* {{ react }}
|
|
1742
1790
|
*/
|
|
1743
1791
|
async grabTextFrom(locator) {
|
|
1744
|
-
const texts = await this.grabTextFromAll(locator)
|
|
1745
|
-
assertElementExists(texts, locator)
|
|
1792
|
+
const texts = await this.grabTextFromAll(locator)
|
|
1793
|
+
assertElementExists(texts, locator)
|
|
1746
1794
|
if (texts.length > 1) {
|
|
1747
|
-
this.debugSection('GrabText', `Using first element out of ${texts.length}`)
|
|
1795
|
+
this.debugSection('GrabText', `Using first element out of ${texts.length}`)
|
|
1748
1796
|
}
|
|
1749
1797
|
|
|
1750
|
-
return texts[0]
|
|
1798
|
+
return texts[0]
|
|
1751
1799
|
}
|
|
1752
1800
|
|
|
1753
1801
|
/**
|
|
1754
1802
|
* {{> grabValueFromAll }}
|
|
1755
1803
|
*/
|
|
1756
1804
|
async grabValueFromAll(locator) {
|
|
1757
|
-
const els = await findFields.call(this, locator)
|
|
1758
|
-
const values = []
|
|
1805
|
+
const els = await findFields.call(this, locator)
|
|
1806
|
+
const values = []
|
|
1759
1807
|
for (const el of els) {
|
|
1760
|
-
values.push(await (await el.getProperty('value')).jsonValue())
|
|
1808
|
+
values.push(await (await el.getProperty('value')).jsonValue())
|
|
1761
1809
|
}
|
|
1762
|
-
return values
|
|
1810
|
+
return values
|
|
1763
1811
|
}
|
|
1764
1812
|
|
|
1765
1813
|
/**
|
|
1766
1814
|
* {{> grabValueFrom }}
|
|
1767
1815
|
*/
|
|
1768
1816
|
async grabValueFrom(locator) {
|
|
1769
|
-
const values = await this.grabValueFromAll(locator)
|
|
1770
|
-
assertElementExists(values, locator)
|
|
1817
|
+
const values = await this.grabValueFromAll(locator)
|
|
1818
|
+
assertElementExists(values, locator)
|
|
1771
1819
|
if (values.length > 1) {
|
|
1772
|
-
this.debugSection('GrabValue', `Using first element out of ${values.length}`)
|
|
1820
|
+
this.debugSection('GrabValue', `Using first element out of ${values.length}`)
|
|
1773
1821
|
}
|
|
1774
1822
|
|
|
1775
|
-
return values[0]
|
|
1823
|
+
return values[0]
|
|
1776
1824
|
}
|
|
1777
1825
|
|
|
1778
1826
|
/**
|
|
1779
1827
|
* {{> grabHTMLFromAll }}
|
|
1780
1828
|
*/
|
|
1781
1829
|
async grabHTMLFromAll(locator) {
|
|
1782
|
-
const els = await this._locate(locator)
|
|
1783
|
-
const values = await Promise.all(els.map(el => el.evaluate(element => element.innerHTML, el)))
|
|
1784
|
-
return values
|
|
1830
|
+
const els = await this._locate(locator)
|
|
1831
|
+
const values = await Promise.all(els.map((el) => el.evaluate((element) => element.innerHTML, el)))
|
|
1832
|
+
return values
|
|
1785
1833
|
}
|
|
1786
1834
|
|
|
1787
1835
|
/**
|
|
1788
1836
|
* {{> grabHTMLFrom }}
|
|
1789
1837
|
*/
|
|
1790
1838
|
async grabHTMLFrom(locator) {
|
|
1791
|
-
const html = await this.grabHTMLFromAll(locator)
|
|
1792
|
-
assertElementExists(html, locator)
|
|
1839
|
+
const html = await this.grabHTMLFromAll(locator)
|
|
1840
|
+
assertElementExists(html, locator)
|
|
1793
1841
|
if (html.length > 1) {
|
|
1794
|
-
this.debugSection('GrabHTML', `Using first element out of ${html.length}`)
|
|
1842
|
+
this.debugSection('GrabHTML', `Using first element out of ${html.length}`)
|
|
1795
1843
|
}
|
|
1796
1844
|
|
|
1797
|
-
return html[0]
|
|
1845
|
+
return html[0]
|
|
1798
1846
|
}
|
|
1799
1847
|
|
|
1800
1848
|
/**
|
|
@@ -1802,11 +1850,13 @@ class Puppeteer extends Helper {
|
|
|
1802
1850
|
* {{ react }}
|
|
1803
1851
|
*/
|
|
1804
1852
|
async grabCssPropertyFromAll(locator, cssProperty) {
|
|
1805
|
-
const els = await this._locate(locator)
|
|
1806
|
-
const res = await Promise.all(
|
|
1807
|
-
|
|
1853
|
+
const els = await this._locate(locator)
|
|
1854
|
+
const res = await Promise.all(
|
|
1855
|
+
els.map((el) => el.evaluate((el) => JSON.parse(JSON.stringify(getComputedStyle(el))), el)),
|
|
1856
|
+
)
|
|
1857
|
+
const cssValues = res.map((props) => props[toCamelCase(cssProperty)])
|
|
1808
1858
|
|
|
1809
|
-
return cssValues
|
|
1859
|
+
return cssValues
|
|
1810
1860
|
}
|
|
1811
1861
|
|
|
1812
1862
|
/**
|
|
@@ -1814,14 +1864,14 @@ class Puppeteer extends Helper {
|
|
|
1814
1864
|
* {{ react }}
|
|
1815
1865
|
*/
|
|
1816
1866
|
async grabCssPropertyFrom(locator, cssProperty) {
|
|
1817
|
-
const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty)
|
|
1818
|
-
assertElementExists(cssValues, locator)
|
|
1867
|
+
const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty)
|
|
1868
|
+
assertElementExists(cssValues, locator)
|
|
1819
1869
|
|
|
1820
1870
|
if (cssValues.length > 1) {
|
|
1821
|
-
this.debugSection('GrabCSS', `Using first element out of ${cssValues.length}`)
|
|
1871
|
+
this.debugSection('GrabCSS', `Using first element out of ${cssValues.length}`)
|
|
1822
1872
|
}
|
|
1823
1873
|
|
|
1824
|
-
return cssValues[0]
|
|
1874
|
+
return cssValues[0]
|
|
1825
1875
|
}
|
|
1826
1876
|
|
|
1827
1877
|
/**
|
|
@@ -1829,35 +1879,37 @@ class Puppeteer extends Helper {
|
|
|
1829
1879
|
* {{ react }}
|
|
1830
1880
|
*/
|
|
1831
1881
|
async seeCssPropertiesOnElements(locator, cssProperties) {
|
|
1832
|
-
const res = await this._locate(locator)
|
|
1833
|
-
assertElementExists(res, locator)
|
|
1882
|
+
const res = await this._locate(locator)
|
|
1883
|
+
assertElementExists(res, locator)
|
|
1834
1884
|
|
|
1835
|
-
const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties)
|
|
1836
|
-
const elemAmount = res.length
|
|
1837
|
-
let props = []
|
|
1885
|
+
const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties)
|
|
1886
|
+
const elemAmount = res.length
|
|
1887
|
+
let props = []
|
|
1838
1888
|
|
|
1839
1889
|
for (const element of res) {
|
|
1840
1890
|
for (const prop of Object.keys(cssProperties)) {
|
|
1841
|
-
const cssProp = await this.grabCssPropertyFrom(locator, prop)
|
|
1891
|
+
const cssProp = await this.grabCssPropertyFrom(locator, prop)
|
|
1842
1892
|
if (isColorProperty(prop)) {
|
|
1843
|
-
props.push(convertColorToRGBA(cssProp))
|
|
1893
|
+
props.push(convertColorToRGBA(cssProp))
|
|
1844
1894
|
} else {
|
|
1845
|
-
props.push(cssProp)
|
|
1895
|
+
props.push(cssProp)
|
|
1846
1896
|
}
|
|
1847
1897
|
}
|
|
1848
1898
|
}
|
|
1849
1899
|
|
|
1850
|
-
const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
|
|
1851
|
-
if (!Array.isArray(props)) props = [props]
|
|
1852
|
-
let chunked = chunkArray(props, values.length)
|
|
1900
|
+
const values = Object.keys(cssPropertiesCamelCase).map((key) => cssPropertiesCamelCase[key])
|
|
1901
|
+
if (!Array.isArray(props)) props = [props]
|
|
1902
|
+
let chunked = chunkArray(props, values.length)
|
|
1853
1903
|
chunked = chunked.filter((val) => {
|
|
1854
1904
|
for (let i = 0; i < val.length; ++i) {
|
|
1855
1905
|
// eslint-disable-next-line eqeqeq
|
|
1856
|
-
if (val[i] != values[i]) return false
|
|
1906
|
+
if (val[i] != values[i]) return false
|
|
1857
1907
|
}
|
|
1858
|
-
return true
|
|
1859
|
-
})
|
|
1860
|
-
return equals(
|
|
1908
|
+
return true
|
|
1909
|
+
})
|
|
1910
|
+
return equals(
|
|
1911
|
+
`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
|
|
1912
|
+
).assert(chunked.length, elemAmount)
|
|
1861
1913
|
}
|
|
1862
1914
|
|
|
1863
1915
|
/**
|
|
@@ -1865,34 +1917,40 @@ class Puppeteer extends Helper {
|
|
|
1865
1917
|
* {{ react }}
|
|
1866
1918
|
*/
|
|
1867
1919
|
async seeAttributesOnElements(locator, attributes) {
|
|
1868
|
-
const elements = await this._locate(locator)
|
|
1869
|
-
assertElementExists(elements, locator)
|
|
1920
|
+
const elements = await this._locate(locator)
|
|
1921
|
+
assertElementExists(elements, locator)
|
|
1870
1922
|
|
|
1871
|
-
const expectedAttributes = Object.entries(attributes)
|
|
1923
|
+
const expectedAttributes = Object.entries(attributes)
|
|
1872
1924
|
|
|
1873
1925
|
const valuesPromises = elements.map(async (element) => {
|
|
1874
|
-
const elementAttributes = {}
|
|
1875
|
-
await Promise.all(
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1926
|
+
const elementAttributes = {}
|
|
1927
|
+
await Promise.all(
|
|
1928
|
+
expectedAttributes.map(async ([attribute, expectedValue]) => {
|
|
1929
|
+
const actualValue = await element.evaluate((el, attr) => el[attr] || el.getAttribute(attr), attribute)
|
|
1930
|
+
elementAttributes[attribute] = actualValue
|
|
1931
|
+
}),
|
|
1932
|
+
)
|
|
1933
|
+
return elementAttributes
|
|
1934
|
+
})
|
|
1881
1935
|
|
|
1882
|
-
const actualAttributes = await Promise.all(valuesPromises)
|
|
1936
|
+
const actualAttributes = await Promise.all(valuesPromises)
|
|
1883
1937
|
|
|
1884
|
-
const matchingElements = actualAttributes.filter((attrs) =>
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1938
|
+
const matchingElements = actualAttributes.filter((attrs) =>
|
|
1939
|
+
expectedAttributes.every(([attribute, expectedValue]) => {
|
|
1940
|
+
const actualValue = attrs[attribute]
|
|
1941
|
+
if (!actualValue) return false
|
|
1942
|
+
if (actualValue.toString().match(new RegExp(expectedValue.toString()))) return true
|
|
1943
|
+
return expectedValue === actualValue
|
|
1944
|
+
}),
|
|
1945
|
+
)
|
|
1890
1946
|
|
|
1891
|
-
const elementsCount = elements.length
|
|
1892
|
-
const matchingCount = matchingElements.length
|
|
1947
|
+
const elementsCount = elements.length
|
|
1948
|
+
const matchingCount = matchingElements.length
|
|
1893
1949
|
|
|
1894
|
-
return equals(`all elements (${
|
|
1895
|
-
|
|
1950
|
+
return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(
|
|
1951
|
+
matchingCount,
|
|
1952
|
+
elementsCount,
|
|
1953
|
+
)
|
|
1896
1954
|
}
|
|
1897
1955
|
|
|
1898
1956
|
/**
|
|
@@ -1900,21 +1958,21 @@ class Puppeteer extends Helper {
|
|
|
1900
1958
|
* {{ react }}
|
|
1901
1959
|
*/
|
|
1902
1960
|
async dragSlider(locator, offsetX = 0) {
|
|
1903
|
-
const src = await this._locate(locator)
|
|
1904
|
-
assertElementExists(src, locator, 'Slider Element')
|
|
1961
|
+
const src = await this._locate(locator)
|
|
1962
|
+
assertElementExists(src, locator, 'Slider Element')
|
|
1905
1963
|
|
|
1906
1964
|
// Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
|
|
1907
|
-
const sliderSource = await getClickablePoint(src[0])
|
|
1965
|
+
const sliderSource = await getClickablePoint(src[0])
|
|
1908
1966
|
|
|
1909
1967
|
// Drag start point
|
|
1910
|
-
await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 })
|
|
1911
|
-
await this.page.mouse.down()
|
|
1968
|
+
await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 })
|
|
1969
|
+
await this.page.mouse.down()
|
|
1912
1970
|
|
|
1913
1971
|
// Drag destination
|
|
1914
|
-
await this.page.mouse.move(sliderSource.x + offsetX, sliderSource.y, { steps: 5 })
|
|
1915
|
-
await this.page.mouse.up()
|
|
1972
|
+
await this.page.mouse.move(sliderSource.x + offsetX, sliderSource.y, { steps: 5 })
|
|
1973
|
+
await this.page.mouse.up()
|
|
1916
1974
|
|
|
1917
|
-
await this._waitForAction()
|
|
1975
|
+
await this._waitForAction()
|
|
1918
1976
|
}
|
|
1919
1977
|
|
|
1920
1978
|
/**
|
|
@@ -1922,13 +1980,13 @@ class Puppeteer extends Helper {
|
|
|
1922
1980
|
* {{ react }}
|
|
1923
1981
|
*/
|
|
1924
1982
|
async grabAttributeFromAll(locator, attr) {
|
|
1925
|
-
const els = await this._locate(locator)
|
|
1926
|
-
const array = []
|
|
1983
|
+
const els = await this._locate(locator)
|
|
1984
|
+
const array = []
|
|
1927
1985
|
for (let index = 0; index < els.length; index++) {
|
|
1928
|
-
const a = await this._evaluateHandeInContext((el, attr) => el[attr] || el.getAttribute(attr), els[index], attr)
|
|
1929
|
-
array.push(await a.jsonValue())
|
|
1986
|
+
const a = await this._evaluateHandeInContext((el, attr) => el[attr] || el.getAttribute(attr), els[index], attr)
|
|
1987
|
+
array.push(await a.jsonValue())
|
|
1930
1988
|
}
|
|
1931
|
-
return array
|
|
1989
|
+
return array
|
|
1932
1990
|
}
|
|
1933
1991
|
|
|
1934
1992
|
/**
|
|
@@ -1936,84 +1994,84 @@ class Puppeteer extends Helper {
|
|
|
1936
1994
|
* {{ react }}
|
|
1937
1995
|
*/
|
|
1938
1996
|
async grabAttributeFrom(locator, attr) {
|
|
1939
|
-
const attrs = await this.grabAttributeFromAll(locator, attr)
|
|
1940
|
-
assertElementExists(attrs, locator)
|
|
1997
|
+
const attrs = await this.grabAttributeFromAll(locator, attr)
|
|
1998
|
+
assertElementExists(attrs, locator)
|
|
1941
1999
|
if (attrs.length > 1) {
|
|
1942
|
-
this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`)
|
|
2000
|
+
this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`)
|
|
1943
2001
|
}
|
|
1944
2002
|
|
|
1945
|
-
return attrs[0]
|
|
2003
|
+
return attrs[0]
|
|
1946
2004
|
}
|
|
1947
2005
|
|
|
1948
2006
|
/**
|
|
1949
2007
|
* {{> saveElementScreenshot }}
|
|
1950
2008
|
*/
|
|
1951
2009
|
async saveElementScreenshot(locator, fileName) {
|
|
1952
|
-
const outputFile = screenshotOutputFolder(fileName)
|
|
2010
|
+
const outputFile = screenshotOutputFolder(fileName)
|
|
1953
2011
|
|
|
1954
|
-
const res = await this._locate(locator)
|
|
1955
|
-
assertElementExists(res, locator)
|
|
1956
|
-
if (res.length > 1) this.debug(`[Elements] Using first element out of ${res.length}`)
|
|
1957
|
-
const elem = res[0]
|
|
1958
|
-
this.debug(`Screenshot of ${
|
|
1959
|
-
return elem.screenshot({ path: outputFile, type: 'png' })
|
|
2012
|
+
const res = await this._locate(locator)
|
|
2013
|
+
assertElementExists(res, locator)
|
|
2014
|
+
if (res.length > 1) this.debug(`[Elements] Using first element out of ${res.length}`)
|
|
2015
|
+
const elem = res[0]
|
|
2016
|
+
this.debug(`Screenshot of ${new Locator(locator)} element has been saved to ${outputFile}`)
|
|
2017
|
+
return elem.screenshot({ path: outputFile, type: 'png' })
|
|
1960
2018
|
}
|
|
1961
2019
|
|
|
1962
2020
|
/**
|
|
1963
2021
|
* {{> saveScreenshot }}
|
|
1964
2022
|
*/
|
|
1965
2023
|
async saveScreenshot(fileName, fullPage) {
|
|
1966
|
-
const fullPageOption = fullPage || this.options.fullPageScreenshots
|
|
1967
|
-
let outputFile = screenshotOutputFolder(fileName)
|
|
2024
|
+
const fullPageOption = fullPage || this.options.fullPageScreenshots
|
|
2025
|
+
let outputFile = screenshotOutputFolder(fileName)
|
|
1968
2026
|
|
|
1969
|
-
this.debug(`Screenshot is saving to ${outputFile}`)
|
|
2027
|
+
this.debug(`Screenshot is saving to ${outputFile}`)
|
|
1970
2028
|
|
|
1971
2029
|
await this.page.screenshot({
|
|
1972
2030
|
path: outputFile,
|
|
1973
2031
|
fullPage: fullPageOption,
|
|
1974
2032
|
type: 'png',
|
|
1975
|
-
})
|
|
2033
|
+
})
|
|
1976
2034
|
|
|
1977
2035
|
if (this.activeSessionName) {
|
|
1978
2036
|
for (const sessionName in this.sessionPages) {
|
|
1979
|
-
const activeSessionPage = this.sessionPages[sessionName]
|
|
1980
|
-
outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`)
|
|
2037
|
+
const activeSessionPage = this.sessionPages[sessionName]
|
|
2038
|
+
outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`)
|
|
1981
2039
|
|
|
1982
|
-
this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`)
|
|
2040
|
+
this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`)
|
|
1983
2041
|
|
|
1984
2042
|
if (activeSessionPage) {
|
|
1985
2043
|
await activeSessionPage.screenshot({
|
|
1986
2044
|
path: outputFile,
|
|
1987
2045
|
fullPage: fullPageOption,
|
|
1988
2046
|
type: 'png',
|
|
1989
|
-
})
|
|
2047
|
+
})
|
|
1990
2048
|
}
|
|
1991
2049
|
}
|
|
1992
2050
|
}
|
|
1993
2051
|
}
|
|
1994
2052
|
|
|
1995
2053
|
async _failed(test) {
|
|
1996
|
-
await this._withinEnd()
|
|
2054
|
+
await this._withinEnd()
|
|
1997
2055
|
|
|
1998
2056
|
if (this.options.trace) {
|
|
1999
|
-
await this.page.tracing.stop()
|
|
2000
|
-
const _traceName = this.currentRunningTest.artifacts.trace.replace('.json', '.failed.json')
|
|
2001
|
-
fs.renameSync(this.currentRunningTest.artifacts.trace, _traceName)
|
|
2002
|
-
test.artifacts.trace = _traceName
|
|
2057
|
+
await this.page.tracing.stop()
|
|
2058
|
+
const _traceName = this.currentRunningTest.artifacts.trace.replace('.json', '.failed.json')
|
|
2059
|
+
fs.renameSync(this.currentRunningTest.artifacts.trace, _traceName)
|
|
2060
|
+
test.artifacts.trace = _traceName
|
|
2003
2061
|
}
|
|
2004
2062
|
}
|
|
2005
2063
|
|
|
2006
2064
|
async _passed(test) {
|
|
2007
|
-
await this._withinEnd()
|
|
2065
|
+
await this._withinEnd()
|
|
2008
2066
|
|
|
2009
2067
|
if (this.options.trace) {
|
|
2010
|
-
await this.page.tracing.stop()
|
|
2068
|
+
await this.page.tracing.stop()
|
|
2011
2069
|
if (this.options.keepTraceForPassedTests) {
|
|
2012
|
-
const _traceName = this.currentRunningTest.artifacts.trace.replace('.json', '.passed.json')
|
|
2013
|
-
fs.renameSync(this.currentRunningTest.artifacts.trace, _traceName)
|
|
2014
|
-
test.artifacts.trace = _traceName
|
|
2070
|
+
const _traceName = this.currentRunningTest.artifacts.trace.replace('.json', '.passed.json')
|
|
2071
|
+
fs.renameSync(this.currentRunningTest.artifacts.trace, _traceName)
|
|
2072
|
+
test.artifacts.trace = _traceName
|
|
2015
2073
|
} else {
|
|
2016
|
-
fs.unlinkSync(this.currentRunningTest.artifacts.trace)
|
|
2074
|
+
fs.unlinkSync(this.currentRunningTest.artifacts.trace)
|
|
2017
2075
|
}
|
|
2018
2076
|
}
|
|
2019
2077
|
}
|
|
@@ -2022,70 +2080,74 @@ class Puppeteer extends Helper {
|
|
|
2022
2080
|
* {{> wait }}
|
|
2023
2081
|
*/
|
|
2024
2082
|
async wait(sec) {
|
|
2025
|
-
return new Promise((
|
|
2026
|
-
setTimeout(done, sec * 1000)
|
|
2027
|
-
})
|
|
2083
|
+
return new Promise((done) => {
|
|
2084
|
+
setTimeout(done, sec * 1000)
|
|
2085
|
+
})
|
|
2028
2086
|
}
|
|
2029
2087
|
|
|
2030
2088
|
/**
|
|
2031
2089
|
* {{> waitForEnabled }}
|
|
2032
2090
|
*/
|
|
2033
2091
|
async waitForEnabled(locator, sec) {
|
|
2034
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2035
|
-
locator = new Locator(locator, 'css')
|
|
2036
|
-
await this.context
|
|
2037
|
-
let waiter
|
|
2038
|
-
const context = await this._getContext()
|
|
2092
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2093
|
+
locator = new Locator(locator, 'css')
|
|
2094
|
+
await this.context
|
|
2095
|
+
let waiter
|
|
2096
|
+
const context = await this._getContext()
|
|
2039
2097
|
if (locator.isCSS()) {
|
|
2040
2098
|
const enabledFn = function (locator) {
|
|
2041
|
-
const els = document.querySelectorAll(locator)
|
|
2099
|
+
const els = document.querySelectorAll(locator)
|
|
2042
2100
|
if (!els || els.length === 0) {
|
|
2043
|
-
return false
|
|
2101
|
+
return false
|
|
2044
2102
|
}
|
|
2045
|
-
return Array.prototype.filter.call(els, el => !el.disabled).length > 0
|
|
2046
|
-
}
|
|
2047
|
-
waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value)
|
|
2103
|
+
return Array.prototype.filter.call(els, (el) => !el.disabled).length > 0
|
|
2104
|
+
}
|
|
2105
|
+
waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value)
|
|
2048
2106
|
} else {
|
|
2049
2107
|
const enabledFn = function (locator, $XPath) {
|
|
2050
|
-
eval($XPath)
|
|
2051
|
-
return $XPath(null, locator).filter(el => !el.disabled).length > 0
|
|
2052
|
-
}
|
|
2053
|
-
waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
|
|
2108
|
+
eval($XPath) // eslint-disable-line no-eval
|
|
2109
|
+
return $XPath(null, locator).filter((el) => !el.disabled).length > 0
|
|
2110
|
+
}
|
|
2111
|
+
waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
|
|
2054
2112
|
}
|
|
2055
2113
|
return waiter.catch((err) => {
|
|
2056
|
-
throw new Error(
|
|
2057
|
-
|
|
2114
|
+
throw new Error(
|
|
2115
|
+
`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2116
|
+
)
|
|
2117
|
+
})
|
|
2058
2118
|
}
|
|
2059
2119
|
|
|
2060
2120
|
/**
|
|
2061
2121
|
* {{> waitForValue }}
|
|
2062
2122
|
*/
|
|
2063
2123
|
async waitForValue(field, value, sec) {
|
|
2064
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2065
|
-
const locator = new Locator(field, 'css')
|
|
2066
|
-
await this.context
|
|
2067
|
-
let waiter
|
|
2068
|
-
const context = await this._getContext()
|
|
2124
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2125
|
+
const locator = new Locator(field, 'css')
|
|
2126
|
+
await this.context
|
|
2127
|
+
let waiter
|
|
2128
|
+
const context = await this._getContext()
|
|
2069
2129
|
if (locator.isCSS()) {
|
|
2070
2130
|
const valueFn = function (locator, value) {
|
|
2071
|
-
const els = document.querySelectorAll(locator)
|
|
2131
|
+
const els = document.querySelectorAll(locator)
|
|
2072
2132
|
if (!els || els.length === 0) {
|
|
2073
|
-
return false
|
|
2133
|
+
return false
|
|
2074
2134
|
}
|
|
2075
|
-
return Array.prototype.filter.call(els, el => (el.value.toString() || '').indexOf(value) !== -1).length > 0
|
|
2076
|
-
}
|
|
2077
|
-
waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, value)
|
|
2135
|
+
return Array.prototype.filter.call(els, (el) => (el.value.toString() || '').indexOf(value) !== -1).length > 0
|
|
2136
|
+
}
|
|
2137
|
+
waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, value)
|
|
2078
2138
|
} else {
|
|
2079
2139
|
const valueFn = function (locator, $XPath, value) {
|
|
2080
|
-
eval($XPath)
|
|
2081
|
-
return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0
|
|
2082
|
-
}
|
|
2083
|
-
waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), value)
|
|
2140
|
+
eval($XPath) // eslint-disable-line no-eval
|
|
2141
|
+
return $XPath(null, locator).filter((el) => (el.value || '').indexOf(value) !== -1).length > 0
|
|
2142
|
+
}
|
|
2143
|
+
waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), value)
|
|
2084
2144
|
}
|
|
2085
2145
|
return waiter.catch((err) => {
|
|
2086
|
-
const loc = locator.toString()
|
|
2087
|
-
throw new Error(
|
|
2088
|
-
|
|
2146
|
+
const loc = locator.toString()
|
|
2147
|
+
throw new Error(
|
|
2148
|
+
`element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2149
|
+
)
|
|
2150
|
+
})
|
|
2089
2151
|
}
|
|
2090
2152
|
|
|
2091
2153
|
/**
|
|
@@ -2093,45 +2155,49 @@ class Puppeteer extends Helper {
|
|
|
2093
2155
|
* {{ react }}
|
|
2094
2156
|
*/
|
|
2095
2157
|
async waitNumberOfVisibleElements(locator, num, sec) {
|
|
2096
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2097
|
-
locator = new Locator(locator, 'css')
|
|
2098
|
-
let waiter
|
|
2099
|
-
const context = await this._getContext()
|
|
2158
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2159
|
+
locator = new Locator(locator, 'css')
|
|
2160
|
+
let waiter
|
|
2161
|
+
const context = await this._getContext()
|
|
2100
2162
|
if (locator.isCSS()) {
|
|
2101
2163
|
const visibleFn = function (locator, num) {
|
|
2102
|
-
const els = document.querySelectorAll(locator)
|
|
2164
|
+
const els = document.querySelectorAll(locator)
|
|
2103
2165
|
if (!els || els.length === 0) {
|
|
2104
|
-
return false
|
|
2166
|
+
return false
|
|
2105
2167
|
}
|
|
2106
|
-
return Array.prototype.filter.call(els, el => el.offsetParent !== null).length === num
|
|
2107
|
-
}
|
|
2108
|
-
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, num)
|
|
2168
|
+
return Array.prototype.filter.call(els, (el) => el.offsetParent !== null).length === num
|
|
2169
|
+
}
|
|
2170
|
+
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, num)
|
|
2109
2171
|
} else {
|
|
2110
2172
|
const visibleFn = function (locator, $XPath, num) {
|
|
2111
|
-
eval($XPath)
|
|
2112
|
-
return $XPath(null, locator).filter(el => el.offsetParent !== null).length === num
|
|
2113
|
-
}
|
|
2114
|
-
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), num)
|
|
2173
|
+
eval($XPath) // eslint-disable-line no-eval
|
|
2174
|
+
return $XPath(null, locator).filter((el) => el.offsetParent !== null).length === num
|
|
2175
|
+
}
|
|
2176
|
+
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), num)
|
|
2115
2177
|
}
|
|
2116
2178
|
return waiter.catch((err) => {
|
|
2117
|
-
throw new Error(
|
|
2118
|
-
|
|
2179
|
+
throw new Error(
|
|
2180
|
+
`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2181
|
+
)
|
|
2182
|
+
})
|
|
2119
2183
|
}
|
|
2120
2184
|
|
|
2121
2185
|
/**
|
|
2122
2186
|
* {{> waitForClickable }}
|
|
2123
2187
|
*/
|
|
2124
2188
|
async waitForClickable(locator, waitTimeout) {
|
|
2125
|
-
const els = await this._locate(locator)
|
|
2126
|
-
assertElementExists(els, locator)
|
|
2189
|
+
const els = await this._locate(locator)
|
|
2190
|
+
assertElementExists(els, locator)
|
|
2127
2191
|
|
|
2128
2192
|
return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
|
|
2129
2193
|
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2130
|
-
throw new Error(
|
|
2194
|
+
throw new Error(
|
|
2195
|
+
`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`,
|
|
2196
|
+
)
|
|
2131
2197
|
} else {
|
|
2132
|
-
throw e
|
|
2198
|
+
throw e
|
|
2133
2199
|
}
|
|
2134
|
-
})
|
|
2200
|
+
})
|
|
2135
2201
|
}
|
|
2136
2202
|
|
|
2137
2203
|
/**
|
|
@@ -2139,19 +2205,21 @@ class Puppeteer extends Helper {
|
|
|
2139
2205
|
* {{ react }}
|
|
2140
2206
|
*/
|
|
2141
2207
|
async waitForElement(locator, sec) {
|
|
2142
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2143
|
-
locator = new Locator(locator, 'css')
|
|
2208
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2209
|
+
locator = new Locator(locator, 'css')
|
|
2144
2210
|
|
|
2145
|
-
let waiter
|
|
2146
|
-
const context = await this._getContext()
|
|
2211
|
+
let waiter
|
|
2212
|
+
const context = await this._getContext()
|
|
2147
2213
|
if (locator.isCSS()) {
|
|
2148
|
-
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout })
|
|
2214
|
+
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout })
|
|
2149
2215
|
} else {
|
|
2150
|
-
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout })
|
|
2216
|
+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout })
|
|
2151
2217
|
}
|
|
2152
2218
|
return waiter.catch((err) => {
|
|
2153
|
-
throw new Error(
|
|
2154
|
-
|
|
2219
|
+
throw new Error(
|
|
2220
|
+
`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2221
|
+
)
|
|
2222
|
+
})
|
|
2155
2223
|
}
|
|
2156
2224
|
|
|
2157
2225
|
/**
|
|
@@ -2160,160 +2228,191 @@ class Puppeteer extends Helper {
|
|
|
2160
2228
|
* {{ react }}
|
|
2161
2229
|
*/
|
|
2162
2230
|
async waitForVisible(locator, sec) {
|
|
2163
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2164
|
-
locator = new Locator(locator, 'css')
|
|
2165
|
-
await this.context
|
|
2166
|
-
let waiter
|
|
2167
|
-
const context = await this._getContext()
|
|
2231
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2232
|
+
locator = new Locator(locator, 'css')
|
|
2233
|
+
await this.context
|
|
2234
|
+
let waiter
|
|
2235
|
+
const context = await this._getContext()
|
|
2168
2236
|
if (locator.isCSS()) {
|
|
2169
|
-
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, visible: true })
|
|
2237
|
+
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, visible: true })
|
|
2170
2238
|
} else {
|
|
2171
|
-
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, visible: true })
|
|
2239
|
+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, visible: true })
|
|
2172
2240
|
}
|
|
2173
2241
|
return waiter.catch((err) => {
|
|
2174
|
-
throw new Error(
|
|
2175
|
-
|
|
2242
|
+
throw new Error(
|
|
2243
|
+
`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2244
|
+
)
|
|
2245
|
+
})
|
|
2176
2246
|
}
|
|
2177
2247
|
|
|
2178
2248
|
/**
|
|
2179
2249
|
* {{> waitForInvisible }}
|
|
2180
2250
|
*/
|
|
2181
2251
|
async waitForInvisible(locator, sec) {
|
|
2182
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2183
|
-
locator = new Locator(locator, 'css')
|
|
2184
|
-
await this.context
|
|
2185
|
-
let waiter
|
|
2186
|
-
const context = await this._getContext()
|
|
2252
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2253
|
+
locator = new Locator(locator, 'css')
|
|
2254
|
+
await this.context
|
|
2255
|
+
let waiter
|
|
2256
|
+
const context = await this._getContext()
|
|
2187
2257
|
if (locator.isCSS()) {
|
|
2188
|
-
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true })
|
|
2258
|
+
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true })
|
|
2189
2259
|
} else {
|
|
2190
|
-
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
|
|
2260
|
+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
|
|
2191
2261
|
}
|
|
2192
2262
|
return waiter.catch((err) => {
|
|
2193
|
-
throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2194
|
-
})
|
|
2263
|
+
throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2264
|
+
})
|
|
2195
2265
|
}
|
|
2196
2266
|
|
|
2197
2267
|
/**
|
|
2198
2268
|
* {{> waitToHide }}
|
|
2199
2269
|
*/
|
|
2200
2270
|
async waitToHide(locator, sec) {
|
|
2201
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2202
|
-
locator = new Locator(locator, 'css')
|
|
2203
|
-
let waiter
|
|
2204
|
-
const context = await this._getContext()
|
|
2271
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2272
|
+
locator = new Locator(locator, 'css')
|
|
2273
|
+
let waiter
|
|
2274
|
+
const context = await this._getContext()
|
|
2205
2275
|
if (locator.isCSS()) {
|
|
2206
|
-
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true })
|
|
2276
|
+
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true })
|
|
2207
2277
|
} else {
|
|
2208
|
-
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
|
|
2278
|
+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
|
|
2209
2279
|
}
|
|
2210
2280
|
return waiter.catch((err) => {
|
|
2211
|
-
throw new Error(
|
|
2212
|
-
|
|
2281
|
+
throw new Error(
|
|
2282
|
+
`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2283
|
+
)
|
|
2284
|
+
})
|
|
2213
2285
|
}
|
|
2214
2286
|
|
|
2215
2287
|
/**
|
|
2216
2288
|
* {{> waitForNumberOfTabs }}
|
|
2217
2289
|
*/
|
|
2218
2290
|
async waitForNumberOfTabs(expectedTabs, sec) {
|
|
2219
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2220
|
-
let currentTabs
|
|
2221
|
-
let count = 0
|
|
2291
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2292
|
+
let currentTabs
|
|
2293
|
+
let count = 0
|
|
2222
2294
|
|
|
2223
2295
|
do {
|
|
2224
|
-
currentTabs = await this.grabNumberOfOpenTabs()
|
|
2225
|
-
await this.wait(1)
|
|
2226
|
-
count += 1000
|
|
2227
|
-
if (currentTabs >= expectedTabs) return
|
|
2228
|
-
} while (count <= waitTimeout)
|
|
2296
|
+
currentTabs = await this.grabNumberOfOpenTabs()
|
|
2297
|
+
await this.wait(1)
|
|
2298
|
+
count += 1000
|
|
2299
|
+
if (currentTabs >= expectedTabs) return
|
|
2300
|
+
} while (count <= waitTimeout)
|
|
2229
2301
|
|
|
2230
|
-
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`)
|
|
2302
|
+
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`)
|
|
2231
2303
|
}
|
|
2232
2304
|
|
|
2233
2305
|
async _getContext() {
|
|
2234
2306
|
if (this.context && this.context.constructor.name === 'CdpFrame') {
|
|
2235
|
-
return this.context
|
|
2307
|
+
return this.context
|
|
2236
2308
|
}
|
|
2237
|
-
return this.page
|
|
2309
|
+
return this.page
|
|
2238
2310
|
}
|
|
2239
2311
|
|
|
2240
2312
|
/**
|
|
2241
2313
|
* {{> waitInUrl }}
|
|
2242
2314
|
*/
|
|
2243
2315
|
async waitInUrl(urlPart, sec = null) {
|
|
2244
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2245
|
-
|
|
2246
|
-
return this.page
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2316
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2317
|
+
|
|
2318
|
+
return this.page
|
|
2319
|
+
.waitForFunction(
|
|
2320
|
+
(urlPart) => {
|
|
2321
|
+
const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
|
|
2322
|
+
return currUrl.indexOf(urlPart) > -1
|
|
2323
|
+
},
|
|
2324
|
+
{ timeout: waitTimeout },
|
|
2325
|
+
urlPart,
|
|
2326
|
+
)
|
|
2327
|
+
.catch(async (e) => {
|
|
2328
|
+
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
|
|
2329
|
+
if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2330
|
+
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
|
|
2331
|
+
} else {
|
|
2332
|
+
throw e
|
|
2333
|
+
}
|
|
2334
|
+
})
|
|
2257
2335
|
}
|
|
2258
2336
|
|
|
2259
2337
|
/**
|
|
2260
2338
|
* {{> waitUrlEquals }}
|
|
2261
2339
|
*/
|
|
2262
2340
|
async waitUrlEquals(urlPart, sec = null) {
|
|
2263
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2341
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2264
2342
|
|
|
2265
|
-
const baseUrl = this.options.url
|
|
2343
|
+
const baseUrl = this.options.url
|
|
2266
2344
|
if (urlPart.indexOf('http') < 0) {
|
|
2267
|
-
urlPart = baseUrl + urlPart
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
return this.page
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2345
|
+
urlPart = baseUrl + urlPart
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
return this.page
|
|
2349
|
+
.waitForFunction(
|
|
2350
|
+
(urlPart) => {
|
|
2351
|
+
const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
|
|
2352
|
+
return currUrl.indexOf(urlPart) > -1
|
|
2353
|
+
},
|
|
2354
|
+
{ timeout: waitTimeout },
|
|
2355
|
+
urlPart,
|
|
2356
|
+
)
|
|
2357
|
+
.catch(async (e) => {
|
|
2358
|
+
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
|
|
2359
|
+
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2360
|
+
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
|
|
2361
|
+
} else {
|
|
2362
|
+
throw e
|
|
2363
|
+
}
|
|
2364
|
+
})
|
|
2281
2365
|
}
|
|
2282
2366
|
|
|
2283
2367
|
/**
|
|
2284
2368
|
* {{> waitForText }}
|
|
2285
2369
|
*/
|
|
2286
2370
|
async waitForText(text, sec = null, context = null) {
|
|
2287
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2288
|
-
let waiter
|
|
2371
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2372
|
+
let waiter
|
|
2289
2373
|
|
|
2290
|
-
const contextObject = await this._getContext()
|
|
2374
|
+
const contextObject = await this._getContext()
|
|
2291
2375
|
|
|
2292
2376
|
if (context) {
|
|
2293
|
-
const locator = new Locator(context, 'css')
|
|
2377
|
+
const locator = new Locator(context, 'css')
|
|
2294
2378
|
if (locator.isCSS()) {
|
|
2295
|
-
waiter = contextObject.waitForFunction(
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2379
|
+
waiter = contextObject.waitForFunction(
|
|
2380
|
+
(locator, text) => {
|
|
2381
|
+
const el = document.querySelector(locator)
|
|
2382
|
+
if (!el) return false
|
|
2383
|
+
return el.innerText.indexOf(text) > -1
|
|
2384
|
+
},
|
|
2385
|
+
{ timeout: waitTimeout },
|
|
2386
|
+
locator.value,
|
|
2387
|
+
text,
|
|
2388
|
+
)
|
|
2300
2389
|
}
|
|
2301
2390
|
|
|
2302
2391
|
if (locator.isXPath()) {
|
|
2303
|
-
waiter = contextObject.waitForFunction(
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2392
|
+
waiter = contextObject.waitForFunction(
|
|
2393
|
+
(locator, text, $XPath) => {
|
|
2394
|
+
eval($XPath) // eslint-disable-line no-eval
|
|
2395
|
+
const el = $XPath(null, locator)
|
|
2396
|
+
if (!el.length) return false
|
|
2397
|
+
return el[0].innerText.indexOf(text) > -1
|
|
2398
|
+
},
|
|
2399
|
+
{ timeout: waitTimeout },
|
|
2400
|
+
locator.value,
|
|
2401
|
+
text,
|
|
2402
|
+
$XPath.toString(),
|
|
2403
|
+
)
|
|
2309
2404
|
}
|
|
2310
2405
|
} else {
|
|
2311
|
-
waiter = contextObject.waitForFunction(
|
|
2406
|
+
waiter = contextObject.waitForFunction(
|
|
2407
|
+
(text) => document.body && document.body.innerText.indexOf(text) > -1,
|
|
2408
|
+
{ timeout: waitTimeout },
|
|
2409
|
+
text,
|
|
2410
|
+
)
|
|
2312
2411
|
}
|
|
2313
2412
|
|
|
2314
2413
|
return waiter.catch((err) => {
|
|
2315
|
-
throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2316
|
-
})
|
|
2414
|
+
throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2415
|
+
})
|
|
2317
2416
|
}
|
|
2318
2417
|
|
|
2319
2418
|
/**
|
|
@@ -2328,8 +2427,8 @@ class Puppeteer extends Helper {
|
|
|
2328
2427
|
* @param {?number} [sec=null] seconds to wait
|
|
2329
2428
|
*/
|
|
2330
2429
|
async waitForRequest(urlOrPredicate, sec = null) {
|
|
2331
|
-
const timeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2332
|
-
return this.page.waitForRequest(urlOrPredicate, { timeout })
|
|
2430
|
+
const timeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2431
|
+
return this.page.waitForRequest(urlOrPredicate, { timeout })
|
|
2333
2432
|
}
|
|
2334
2433
|
|
|
2335
2434
|
/**
|
|
@@ -2344,8 +2443,8 @@ class Puppeteer extends Helper {
|
|
|
2344
2443
|
* @param {?number} [sec=null] number of seconds to wait
|
|
2345
2444
|
*/
|
|
2346
2445
|
async waitForResponse(urlOrPredicate, sec = null) {
|
|
2347
|
-
const timeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2348
|
-
return this.page.waitForResponse(urlOrPredicate, { timeout })
|
|
2446
|
+
const timeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2447
|
+
return this.page.waitForResponse(urlOrPredicate, { timeout })
|
|
2349
2448
|
}
|
|
2350
2449
|
|
|
2351
2450
|
/**
|
|
@@ -2354,37 +2453,37 @@ class Puppeteer extends Helper {
|
|
|
2354
2453
|
async switchTo(locator) {
|
|
2355
2454
|
if (Number.isInteger(locator)) {
|
|
2356
2455
|
// Select by frame index of current context
|
|
2357
|
-
let frames = []
|
|
2456
|
+
let frames = []
|
|
2358
2457
|
if (this.context && typeof this.context.childFrames === 'function') {
|
|
2359
|
-
frames = await this.context.childFrames()
|
|
2458
|
+
frames = await this.context.childFrames()
|
|
2360
2459
|
} else {
|
|
2361
|
-
frames = await this.page.mainFrame().childFrames()
|
|
2460
|
+
frames = await this.page.mainFrame().childFrames()
|
|
2362
2461
|
}
|
|
2363
2462
|
|
|
2364
2463
|
if (locator >= 0 && locator < frames.length) {
|
|
2365
|
-
this.context = frames[locator]
|
|
2464
|
+
this.context = frames[locator]
|
|
2366
2465
|
} else {
|
|
2367
|
-
throw new Error('Frame index out of bounds')
|
|
2466
|
+
throw new Error('Frame index out of bounds')
|
|
2368
2467
|
}
|
|
2369
|
-
return
|
|
2468
|
+
return
|
|
2370
2469
|
}
|
|
2371
2470
|
|
|
2372
2471
|
if (!locator) {
|
|
2373
|
-
this.context = await this.page.mainFrame()
|
|
2374
|
-
return
|
|
2472
|
+
this.context = await this.page.mainFrame()
|
|
2473
|
+
return
|
|
2375
2474
|
}
|
|
2376
2475
|
|
|
2377
2476
|
// Select iframe by selector
|
|
2378
|
-
const els = await this._locate(locator)
|
|
2379
|
-
assertElementExists(els, locator)
|
|
2477
|
+
const els = await this._locate(locator)
|
|
2478
|
+
assertElementExists(els, locator)
|
|
2380
2479
|
|
|
2381
|
-
const iframeElement = els[0]
|
|
2382
|
-
const contentFrame = await iframeElement.contentFrame()
|
|
2480
|
+
const iframeElement = els[0]
|
|
2481
|
+
const contentFrame = await iframeElement.contentFrame()
|
|
2383
2482
|
|
|
2384
2483
|
if (contentFrame) {
|
|
2385
|
-
this.context = contentFrame
|
|
2484
|
+
this.context = contentFrame
|
|
2386
2485
|
} else {
|
|
2387
|
-
throw new Error('Element "#invalidIframeSelector" was not found by text|CSS|XPath')
|
|
2486
|
+
throw new Error('Element "#invalidIframeSelector" was not found by text|CSS|XPath')
|
|
2388
2487
|
}
|
|
2389
2488
|
}
|
|
2390
2489
|
|
|
@@ -2392,17 +2491,17 @@ class Puppeteer extends Helper {
|
|
|
2392
2491
|
* {{> waitForFunction }}
|
|
2393
2492
|
*/
|
|
2394
2493
|
async waitForFunction(fn, argsOrSec = null, sec = null) {
|
|
2395
|
-
let args = []
|
|
2494
|
+
let args = []
|
|
2396
2495
|
if (argsOrSec) {
|
|
2397
2496
|
if (Array.isArray(argsOrSec)) {
|
|
2398
|
-
args = argsOrSec
|
|
2497
|
+
args = argsOrSec
|
|
2399
2498
|
} else if (typeof argsOrSec === 'number') {
|
|
2400
|
-
sec = argsOrSec
|
|
2499
|
+
sec = argsOrSec
|
|
2401
2500
|
}
|
|
2402
2501
|
}
|
|
2403
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2404
|
-
const context = await this._getContext()
|
|
2405
|
-
return context.waitForFunction(fn, { timeout: waitTimeout }, ...args)
|
|
2502
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2503
|
+
const context = await this._getContext()
|
|
2504
|
+
return context.waitForFunction(fn, { timeout: waitTimeout }, ...args)
|
|
2406
2505
|
}
|
|
2407
2506
|
|
|
2408
2507
|
/**
|
|
@@ -2417,87 +2516,87 @@ class Puppeteer extends Helper {
|
|
|
2417
2516
|
timeout: this.options.getPageTimeout,
|
|
2418
2517
|
waitUntil: this.options.waitForNavigation,
|
|
2419
2518
|
...opts,
|
|
2420
|
-
}
|
|
2421
|
-
return this.page.waitForNavigation(opts)
|
|
2519
|
+
}
|
|
2520
|
+
return this.page.waitForNavigation(opts)
|
|
2422
2521
|
}
|
|
2423
2522
|
|
|
2424
2523
|
async waitUntilExists(locator, sec) {
|
|
2425
2524
|
console.log(`waitUntilExists deprecated:
|
|
2426
2525
|
* use 'waitForElement' to wait for element to be attached
|
|
2427
|
-
* use 'waitForDetached to wait for element to be removed'`)
|
|
2428
|
-
return this.waitForDetached(locator, sec)
|
|
2526
|
+
* use 'waitForDetached to wait for element to be removed'`)
|
|
2527
|
+
return this.waitForDetached(locator, sec)
|
|
2429
2528
|
}
|
|
2430
2529
|
|
|
2431
2530
|
/**
|
|
2432
2531
|
* {{> waitForDetached }}
|
|
2433
2532
|
*/
|
|
2434
2533
|
async waitForDetached(locator, sec) {
|
|
2435
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2436
|
-
locator = new Locator(locator, 'css')
|
|
2534
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2535
|
+
locator = new Locator(locator, 'css')
|
|
2437
2536
|
|
|
2438
|
-
let waiter
|
|
2439
|
-
const context = await this._getContext()
|
|
2537
|
+
let waiter
|
|
2538
|
+
const context = await this._getContext()
|
|
2440
2539
|
if (locator.isCSS()) {
|
|
2441
2540
|
const visibleFn = function (locator) {
|
|
2442
|
-
return document.querySelector(locator) === null
|
|
2443
|
-
}
|
|
2444
|
-
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value)
|
|
2541
|
+
return document.querySelector(locator) === null
|
|
2542
|
+
}
|
|
2543
|
+
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value)
|
|
2445
2544
|
} else {
|
|
2446
2545
|
const visibleFn = function (locator, $XPath) {
|
|
2447
|
-
eval($XPath)
|
|
2448
|
-
return $XPath(null, locator).length === 0
|
|
2449
|
-
}
|
|
2450
|
-
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
|
|
2546
|
+
eval($XPath) // eslint-disable-line no-eval
|
|
2547
|
+
return $XPath(null, locator).length === 0
|
|
2548
|
+
}
|
|
2549
|
+
waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
|
|
2451
2550
|
}
|
|
2452
2551
|
return waiter.catch((err) => {
|
|
2453
|
-
throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2454
|
-
})
|
|
2552
|
+
throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2553
|
+
})
|
|
2455
2554
|
}
|
|
2456
2555
|
|
|
2457
2556
|
async _waitForAction() {
|
|
2458
|
-
return this.wait(this.options.waitForAction / 1000)
|
|
2557
|
+
return this.wait(this.options.waitForAction / 1000)
|
|
2459
2558
|
}
|
|
2460
2559
|
|
|
2461
2560
|
/**
|
|
2462
2561
|
* {{> grabDataFromPerformanceTiming }}
|
|
2463
2562
|
*/
|
|
2464
2563
|
async grabDataFromPerformanceTiming() {
|
|
2465
|
-
return perfTiming
|
|
2564
|
+
return perfTiming
|
|
2466
2565
|
}
|
|
2467
2566
|
|
|
2468
2567
|
/**
|
|
2469
2568
|
* {{> grabElementBoundingRect }}
|
|
2470
2569
|
*/
|
|
2471
2570
|
async grabElementBoundingRect(locator, prop) {
|
|
2472
|
-
const els = await this._locate(locator)
|
|
2473
|
-
assertElementExists(els, locator)
|
|
2474
|
-
const rect = await els[0].boundingBox()
|
|
2475
|
-
if (prop) return rect[prop]
|
|
2476
|
-
return rect
|
|
2571
|
+
const els = await this._locate(locator)
|
|
2572
|
+
assertElementExists(els, locator)
|
|
2573
|
+
const rect = await els[0].boundingBox()
|
|
2574
|
+
if (prop) return rect[prop]
|
|
2575
|
+
return rect
|
|
2477
2576
|
}
|
|
2478
2577
|
|
|
2479
2578
|
/**
|
|
2480
|
-
* Mocks network request using [`Request Interception`](https://pptr.dev/
|
|
2579
|
+
* Mocks network request using [`Request Interception`](https://pptr.dev/guides/network-interception)
|
|
2481
2580
|
*
|
|
2482
2581
|
* ```js
|
|
2483
2582
|
* I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
|
|
2484
2583
|
* ```
|
|
2485
|
-
* This method allows intercepting and mocking requests & responses. [Learn more about it](https://pptr.dev/
|
|
2584
|
+
* This method allows intercepting and mocking requests & responses. [Learn more about it](https://pptr.dev/guides/network-interception)
|
|
2486
2585
|
*
|
|
2487
2586
|
* @param {string|RegExp} [url] URL, regex or pattern for to match URL
|
|
2488
2587
|
* @param {function} [handler] a function to process request
|
|
2489
2588
|
*/
|
|
2490
2589
|
async mockRoute(url, handler) {
|
|
2491
|
-
await this.page.setRequestInterception(true)
|
|
2590
|
+
await this.page.setRequestInterception(true)
|
|
2492
2591
|
|
|
2493
|
-
this.page.on('request', interceptedRequest => {
|
|
2592
|
+
this.page.on('request', (interceptedRequest) => {
|
|
2494
2593
|
if (interceptedRequest.url().match(url)) {
|
|
2495
2594
|
// @ts-ignore
|
|
2496
|
-
handler(interceptedRequest)
|
|
2595
|
+
handler(interceptedRequest)
|
|
2497
2596
|
} else {
|
|
2498
|
-
interceptedRequest.continue()
|
|
2597
|
+
interceptedRequest.continue()
|
|
2499
2598
|
}
|
|
2500
|
-
})
|
|
2599
|
+
})
|
|
2501
2600
|
}
|
|
2502
2601
|
|
|
2503
2602
|
/**
|
|
@@ -2510,18 +2609,18 @@ class Puppeteer extends Helper {
|
|
|
2510
2609
|
* @param {string|RegExp} [url] URL, regex or pattern for to match URL
|
|
2511
2610
|
*/
|
|
2512
2611
|
async stopMockingRoute(url) {
|
|
2513
|
-
await this.page.setRequestInterception(true)
|
|
2612
|
+
await this.page.setRequestInterception(true)
|
|
2514
2613
|
|
|
2515
|
-
this.page.off('request')
|
|
2614
|
+
this.page.off('request')
|
|
2516
2615
|
|
|
2517
2616
|
// Resume normal request handling for the given URL
|
|
2518
|
-
this.page.on('request', interceptedRequest => {
|
|
2617
|
+
this.page.on('request', (interceptedRequest) => {
|
|
2519
2618
|
if (interceptedRequest.url().includes(url)) {
|
|
2520
|
-
interceptedRequest.continue()
|
|
2619
|
+
interceptedRequest.continue()
|
|
2521
2620
|
} else {
|
|
2522
|
-
interceptedRequest.continue()
|
|
2621
|
+
interceptedRequest.continue()
|
|
2523
2622
|
}
|
|
2524
|
-
})
|
|
2623
|
+
})
|
|
2525
2624
|
}
|
|
2526
2625
|
|
|
2527
2626
|
/**
|
|
@@ -2529,7 +2628,7 @@ class Puppeteer extends Helper {
|
|
|
2529
2628
|
* {{> flushNetworkTraffics }}
|
|
2530
2629
|
*/
|
|
2531
2630
|
flushNetworkTraffics() {
|
|
2532
|
-
flushNetworkTraffics.call(this)
|
|
2631
|
+
flushNetworkTraffics.call(this)
|
|
2533
2632
|
}
|
|
2534
2633
|
|
|
2535
2634
|
/**
|
|
@@ -2537,7 +2636,7 @@ class Puppeteer extends Helper {
|
|
|
2537
2636
|
* {{> stopRecordingTraffic }}
|
|
2538
2637
|
*/
|
|
2539
2638
|
stopRecordingTraffic() {
|
|
2540
|
-
stopRecordingTraffic.call(this)
|
|
2639
|
+
stopRecordingTraffic.call(this)
|
|
2541
2640
|
}
|
|
2542
2641
|
|
|
2543
2642
|
/**
|
|
@@ -2545,11 +2644,11 @@ class Puppeteer extends Helper {
|
|
|
2545
2644
|
*
|
|
2546
2645
|
*/
|
|
2547
2646
|
async startRecordingTraffic() {
|
|
2548
|
-
this.flushNetworkTraffics()
|
|
2549
|
-
this.recording = true
|
|
2550
|
-
this.recordedAtLeastOnce = true
|
|
2647
|
+
this.flushNetworkTraffics()
|
|
2648
|
+
this.recording = true
|
|
2649
|
+
this.recordedAtLeastOnce = true
|
|
2551
2650
|
|
|
2552
|
-
await this.page.setRequestInterception(true)
|
|
2651
|
+
await this.page.setRequestInterception(true)
|
|
2553
2652
|
|
|
2554
2653
|
this.page.on('request', (request) => {
|
|
2555
2654
|
const information = {
|
|
@@ -2558,16 +2657,16 @@ class Puppeteer extends Helper {
|
|
|
2558
2657
|
requestHeaders: request.headers(),
|
|
2559
2658
|
requestPostData: request.postData(),
|
|
2560
2659
|
response: request.response(),
|
|
2561
|
-
}
|
|
2660
|
+
}
|
|
2562
2661
|
|
|
2563
|
-
this.debugSection('REQUEST: ', JSON.stringify(information))
|
|
2662
|
+
this.debugSection('REQUEST: ', JSON.stringify(information))
|
|
2564
2663
|
|
|
2565
2664
|
if (typeof information.requestPostData === 'object') {
|
|
2566
|
-
information.requestPostData = JSON.parse(information.requestPostData)
|
|
2665
|
+
information.requestPostData = JSON.parse(information.requestPostData)
|
|
2567
2666
|
}
|
|
2568
|
-
request.continue()
|
|
2569
|
-
this.requests.push(information)
|
|
2570
|
-
})
|
|
2667
|
+
request.continue()
|
|
2668
|
+
this.requests.push(information)
|
|
2669
|
+
})
|
|
2571
2670
|
}
|
|
2572
2671
|
|
|
2573
2672
|
/**
|
|
@@ -2575,17 +2674,15 @@ class Puppeteer extends Helper {
|
|
|
2575
2674
|
* {{> grabRecordedNetworkTraffics }}
|
|
2576
2675
|
*/
|
|
2577
2676
|
async grabRecordedNetworkTraffics() {
|
|
2578
|
-
return grabRecordedNetworkTraffics.call(this)
|
|
2677
|
+
return grabRecordedNetworkTraffics.call(this)
|
|
2579
2678
|
}
|
|
2580
2679
|
|
|
2581
2680
|
/**
|
|
2582
2681
|
*
|
|
2583
2682
|
* {{> seeTraffic }}
|
|
2584
2683
|
*/
|
|
2585
|
-
async seeTraffic({
|
|
2586
|
-
|
|
2587
|
-
}) {
|
|
2588
|
-
await seeTraffic.call(this, ...arguments);
|
|
2684
|
+
async seeTraffic({ name, url, parameters, requestPostData, timeout = 10 }) {
|
|
2685
|
+
await seeTraffic.call(this, ...arguments)
|
|
2589
2686
|
}
|
|
2590
2687
|
|
|
2591
2688
|
/**
|
|
@@ -2594,56 +2691,47 @@ class Puppeteer extends Helper {
|
|
|
2594
2691
|
*
|
|
2595
2692
|
*/
|
|
2596
2693
|
dontSeeTraffic({ name, url }) {
|
|
2597
|
-
dontSeeTraffic.call(this, ...arguments)
|
|
2694
|
+
dontSeeTraffic.call(this, ...arguments)
|
|
2598
2695
|
}
|
|
2599
2696
|
|
|
2600
2697
|
async getNewCDPSession() {
|
|
2601
|
-
const client = await this.page.target().createCDPSession()
|
|
2602
|
-
return client
|
|
2698
|
+
const client = await this.page.target().createCDPSession()
|
|
2699
|
+
return client
|
|
2603
2700
|
}
|
|
2604
2701
|
|
|
2605
2702
|
/**
|
|
2606
2703
|
* {{> startRecordingWebSocketMessages }}
|
|
2607
2704
|
*/
|
|
2608
2705
|
async startRecordingWebSocketMessages() {
|
|
2609
|
-
this.flushWebSocketMessages()
|
|
2610
|
-
this.recordingWebSocketMessages = true
|
|
2611
|
-
this.recordedWebSocketMessagesAtLeastOnce = true
|
|
2612
|
-
|
|
2613
|
-
this.cdpSession = await this.getNewCDPSession();
|
|
2614
|
-
await this.cdpSession.send('Network.enable');
|
|
2615
|
-
await this.cdpSession.send('Page.enable');
|
|
2616
|
-
|
|
2617
|
-
this.cdpSession.on(
|
|
2618
|
-
'Network.webSocketFrameReceived',
|
|
2619
|
-
(payload) => {
|
|
2620
|
-
this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload));
|
|
2621
|
-
},
|
|
2622
|
-
);
|
|
2706
|
+
this.flushWebSocketMessages()
|
|
2707
|
+
this.recordingWebSocketMessages = true
|
|
2708
|
+
this.recordedWebSocketMessagesAtLeastOnce = true
|
|
2623
2709
|
|
|
2624
|
-
this.cdpSession.
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
this._logWebsocketMessages(this._getWebSocketLog('SENT', payload));
|
|
2628
|
-
},
|
|
2629
|
-
);
|
|
2710
|
+
this.cdpSession = await this.getNewCDPSession()
|
|
2711
|
+
await this.cdpSession.send('Network.enable')
|
|
2712
|
+
await this.cdpSession.send('Page.enable')
|
|
2630
2713
|
|
|
2631
|
-
this.cdpSession.on(
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2714
|
+
this.cdpSession.on('Network.webSocketFrameReceived', (payload) => {
|
|
2715
|
+
this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload))
|
|
2716
|
+
})
|
|
2717
|
+
|
|
2718
|
+
this.cdpSession.on('Network.webSocketFrameSent', (payload) => {
|
|
2719
|
+
this._logWebsocketMessages(this._getWebSocketLog('SENT', payload))
|
|
2720
|
+
})
|
|
2721
|
+
|
|
2722
|
+
this.cdpSession.on('Network.webSocketFrameError', (payload) => {
|
|
2723
|
+
this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload))
|
|
2724
|
+
})
|
|
2637
2725
|
}
|
|
2638
2726
|
|
|
2639
2727
|
/**
|
|
2640
2728
|
* {{> stopRecordingWebSocketMessages }}
|
|
2641
2729
|
*/
|
|
2642
2730
|
async stopRecordingWebSocketMessages() {
|
|
2643
|
-
await this.cdpSession.send('Network.disable')
|
|
2644
|
-
await this.cdpSession.send('Page.disable')
|
|
2645
|
-
this.page.removeAllListeners('Network')
|
|
2646
|
-
this.recordingWebSocketMessages = false
|
|
2731
|
+
await this.cdpSession.send('Network.disable')
|
|
2732
|
+
await this.cdpSession.send('Page.disable')
|
|
2733
|
+
this.page.removeAllListeners('Network')
|
|
2734
|
+
this.recordingWebSocketMessages = false
|
|
2647
2735
|
}
|
|
2648
2736
|
|
|
2649
2737
|
/**
|
|
@@ -2655,394 +2743,404 @@ class Puppeteer extends Helper {
|
|
|
2655
2743
|
grabWebSocketMessages() {
|
|
2656
2744
|
if (!this.recordingWebSocketMessages) {
|
|
2657
2745
|
if (!this.recordedWebSocketMessagesAtLeastOnce) {
|
|
2658
|
-
throw new Error(
|
|
2746
|
+
throw new Error(
|
|
2747
|
+
'Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.',
|
|
2748
|
+
)
|
|
2659
2749
|
}
|
|
2660
2750
|
}
|
|
2661
|
-
return this.webSocketMessages
|
|
2751
|
+
return this.webSocketMessages
|
|
2662
2752
|
}
|
|
2663
2753
|
|
|
2664
2754
|
/**
|
|
2665
2755
|
* Resets all recorded WS messages.
|
|
2666
2756
|
*/
|
|
2667
2757
|
flushWebSocketMessages() {
|
|
2668
|
-
this.webSocketMessages = []
|
|
2758
|
+
this.webSocketMessages = []
|
|
2669
2759
|
}
|
|
2670
2760
|
|
|
2671
2761
|
_getWebSocketMessage(payload) {
|
|
2672
2762
|
if (payload.errorMessage) {
|
|
2673
|
-
return payload.errorMessage
|
|
2763
|
+
return payload.errorMessage
|
|
2674
2764
|
}
|
|
2675
2765
|
|
|
2676
|
-
return payload.response.payloadData
|
|
2766
|
+
return payload.response.payloadData
|
|
2677
2767
|
}
|
|
2678
2768
|
|
|
2679
2769
|
_getWebSocketLog(prefix, payload) {
|
|
2680
|
-
return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n
|
|
2770
|
+
return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n`
|
|
2681
2771
|
}
|
|
2682
2772
|
|
|
2683
2773
|
_logWebsocketMessages(message) {
|
|
2684
|
-
this.webSocketMessages
|
|
2774
|
+
this.webSocketMessages.push(message)
|
|
2685
2775
|
}
|
|
2686
2776
|
}
|
|
2687
2777
|
|
|
2688
|
-
|
|
2778
|
+
module.exports = Puppeteer
|
|
2689
2779
|
|
|
2690
2780
|
async function findElements(matcher, locator) {
|
|
2691
|
-
if (locator.react) return findReactElements.call(this, locator)
|
|
2692
|
-
locator = new Locator(locator, 'css')
|
|
2693
|
-
if (!locator.isXPath()) return matcher.$$(locator.simplify())
|
|
2781
|
+
if (locator.react) return findReactElements.call(this, locator)
|
|
2782
|
+
locator = new Locator(locator, 'css')
|
|
2783
|
+
if (!locator.isXPath()) return matcher.$$(locator.simplify())
|
|
2694
2784
|
// puppeteer version < 19.4.0 is no longer supported. This one is backward support.
|
|
2695
2785
|
if (puppeteer.default?.defaultBrowserRevision) {
|
|
2696
|
-
return matcher.$$(`xpath/${locator.value}`)
|
|
2786
|
+
return matcher.$$(`xpath/${locator.value}`)
|
|
2697
2787
|
}
|
|
2698
|
-
return matcher.$x(locator.value)
|
|
2788
|
+
return matcher.$x(locator.value)
|
|
2699
2789
|
}
|
|
2700
2790
|
|
|
2701
2791
|
async function proceedClick(locator, context = null, options = {}) {
|
|
2702
|
-
let matcher = await this.context
|
|
2792
|
+
let matcher = await this.context
|
|
2703
2793
|
if (context) {
|
|
2704
|
-
const els = await this._locate(context)
|
|
2705
|
-
assertElementExists(els, context)
|
|
2706
|
-
matcher = els[0]
|
|
2794
|
+
const els = await this._locate(context)
|
|
2795
|
+
assertElementExists(els, context)
|
|
2796
|
+
matcher = els[0]
|
|
2707
2797
|
}
|
|
2708
|
-
const els = await findClickable.call(this, matcher, locator)
|
|
2798
|
+
const els = await findClickable.call(this, matcher, locator)
|
|
2709
2799
|
if (context) {
|
|
2710
|
-
assertElementExists(
|
|
2800
|
+
assertElementExists(
|
|
2801
|
+
els,
|
|
2802
|
+
locator,
|
|
2803
|
+
'Clickable element',
|
|
2804
|
+
`was not found inside element ${new Locator(context).toString()}`,
|
|
2805
|
+
)
|
|
2711
2806
|
} else {
|
|
2712
|
-
assertElementExists(els, locator, 'Clickable element')
|
|
2807
|
+
assertElementExists(els, locator, 'Clickable element')
|
|
2713
2808
|
}
|
|
2714
2809
|
|
|
2715
|
-
highlightActiveElement.call(this, els[0], await this._getContext())
|
|
2810
|
+
highlightActiveElement.call(this, els[0], await this._getContext())
|
|
2716
2811
|
|
|
2717
|
-
await els[0].click(options)
|
|
2718
|
-
const promises = []
|
|
2812
|
+
await els[0].click(options)
|
|
2813
|
+
const promises = []
|
|
2719
2814
|
if (options.waitForNavigation) {
|
|
2720
|
-
promises.push(this.waitForNavigation())
|
|
2815
|
+
promises.push(this.waitForNavigation())
|
|
2721
2816
|
}
|
|
2722
|
-
promises.push(this._waitForAction())
|
|
2817
|
+
promises.push(this._waitForAction())
|
|
2723
2818
|
|
|
2724
|
-
return Promise.all(promises)
|
|
2819
|
+
return Promise.all(promises)
|
|
2725
2820
|
}
|
|
2726
2821
|
|
|
2727
2822
|
async function findClickable(matcher, locator) {
|
|
2728
|
-
if (locator.react) return findReactElements.call(this, locator)
|
|
2729
|
-
locator = new Locator(locator)
|
|
2730
|
-
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator)
|
|
2823
|
+
if (locator.react) return findReactElements.call(this, locator)
|
|
2824
|
+
locator = new Locator(locator)
|
|
2825
|
+
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator)
|
|
2731
2826
|
|
|
2732
|
-
let els
|
|
2733
|
-
const literal = xpathLocator.literal(locator.value)
|
|
2827
|
+
let els
|
|
2828
|
+
const literal = xpathLocator.literal(locator.value)
|
|
2734
2829
|
|
|
2735
|
-
els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
|
|
2736
|
-
if (els.length) return els
|
|
2830
|
+
els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
|
|
2831
|
+
if (els.length) return els
|
|
2737
2832
|
|
|
2738
|
-
els = await findElements.call(this, matcher, Locator.clickable.wide(literal))
|
|
2739
|
-
if (els.length) return els
|
|
2833
|
+
els = await findElements.call(this, matcher, Locator.clickable.wide(literal))
|
|
2834
|
+
if (els.length) return els
|
|
2740
2835
|
|
|
2741
2836
|
try {
|
|
2742
|
-
els = await findElements.call(this, matcher, Locator.clickable.self(literal))
|
|
2743
|
-
if (els.length) return els
|
|
2837
|
+
els = await findElements.call(this, matcher, Locator.clickable.self(literal))
|
|
2838
|
+
if (els.length) return els
|
|
2744
2839
|
} catch (err) {
|
|
2745
2840
|
// Do nothing
|
|
2746
2841
|
}
|
|
2747
2842
|
|
|
2748
|
-
return findElements.call(this, matcher, locator.value)
|
|
2843
|
+
return findElements.call(this, matcher, locator.value) // by css or xpath
|
|
2749
2844
|
}
|
|
2750
2845
|
|
|
2751
2846
|
async function proceedSee(assertType, text, context, strict = false) {
|
|
2752
|
-
let description
|
|
2753
|
-
let allText
|
|
2847
|
+
let description
|
|
2848
|
+
let allText
|
|
2754
2849
|
if (!context) {
|
|
2755
|
-
let el = await this.context
|
|
2850
|
+
let el = await this.context
|
|
2756
2851
|
|
|
2757
2852
|
if (el && !el.getProperty) {
|
|
2758
2853
|
// Fallback to body
|
|
2759
|
-
el = await this.context.$('body')
|
|
2854
|
+
el = await this.context.$('body')
|
|
2760
2855
|
}
|
|
2761
2856
|
|
|
2762
|
-
allText = [await el.getProperty('innerText').then(p => p.jsonValue())]
|
|
2763
|
-
description = 'web application'
|
|
2857
|
+
allText = [await el.getProperty('innerText').then((p) => p.jsonValue())]
|
|
2858
|
+
description = 'web application'
|
|
2764
2859
|
} else {
|
|
2765
|
-
const locator = new Locator(context, 'css')
|
|
2766
|
-
description = `element ${locator.toString()}
|
|
2767
|
-
const els = await this._locate(locator)
|
|
2768
|
-
assertElementExists(els, locator.toString())
|
|
2769
|
-
allText = await Promise.all(els.map(el => el.getProperty('innerText').then(p => p.jsonValue())))
|
|
2860
|
+
const locator = new Locator(context, 'css')
|
|
2861
|
+
description = `element ${locator.toString()}`
|
|
2862
|
+
const els = await this._locate(locator)
|
|
2863
|
+
assertElementExists(els, locator.toString())
|
|
2864
|
+
allText = await Promise.all(els.map((el) => el.getProperty('innerText').then((p) => p.jsonValue())))
|
|
2770
2865
|
}
|
|
2771
2866
|
|
|
2772
2867
|
if (strict) {
|
|
2773
|
-
return allText.map(elText => equals(description)[assertType](text, elText))
|
|
2868
|
+
return allText.map((elText) => equals(description)[assertType](text, elText))
|
|
2774
2869
|
}
|
|
2775
|
-
return stringIncludes(description)[assertType](
|
|
2870
|
+
return stringIncludes(description)[assertType](
|
|
2871
|
+
normalizeSpacesInString(text),
|
|
2872
|
+
normalizeSpacesInString(allText.join(' | ')),
|
|
2873
|
+
)
|
|
2776
2874
|
}
|
|
2777
2875
|
|
|
2778
2876
|
async function findCheckable(locator, context) {
|
|
2779
|
-
let contextEl = await this.context
|
|
2877
|
+
let contextEl = await this.context
|
|
2780
2878
|
if (typeof context === 'string') {
|
|
2781
|
-
contextEl = await findElements.call(this, contextEl,
|
|
2782
|
-
contextEl = contextEl[0]
|
|
2879
|
+
contextEl = await findElements.call(this, contextEl, new Locator(context, 'css').simplify())
|
|
2880
|
+
contextEl = contextEl[0]
|
|
2783
2881
|
}
|
|
2784
2882
|
|
|
2785
|
-
const matchedLocator = new Locator(locator)
|
|
2883
|
+
const matchedLocator = new Locator(locator)
|
|
2786
2884
|
if (!matchedLocator.isFuzzy()) {
|
|
2787
|
-
return findElements.call(this, contextEl, matchedLocator.simplify())
|
|
2885
|
+
return findElements.call(this, contextEl, matchedLocator.simplify())
|
|
2788
2886
|
}
|
|
2789
2887
|
|
|
2790
|
-
const literal = xpathLocator.literal(locator)
|
|
2791
|
-
let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal))
|
|
2888
|
+
const literal = xpathLocator.literal(locator)
|
|
2889
|
+
let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal))
|
|
2792
2890
|
if (els.length) {
|
|
2793
|
-
return els
|
|
2891
|
+
return els
|
|
2794
2892
|
}
|
|
2795
|
-
els = await findElements.call(this, contextEl, Locator.checkable.byName(literal))
|
|
2893
|
+
els = await findElements.call(this, contextEl, Locator.checkable.byName(literal))
|
|
2796
2894
|
if (els.length) {
|
|
2797
|
-
return els
|
|
2895
|
+
return els
|
|
2798
2896
|
}
|
|
2799
|
-
return findElements.call(this, contextEl, locator)
|
|
2897
|
+
return findElements.call(this, contextEl, locator)
|
|
2800
2898
|
}
|
|
2801
2899
|
|
|
2802
2900
|
async function proceedIsChecked(assertType, option) {
|
|
2803
|
-
let els = await findCheckable.call(this, option)
|
|
2804
|
-
assertElementExists(els, option, 'Checkable')
|
|
2805
|
-
els = await Promise.all(els.map(el => el.getProperty('checked')))
|
|
2806
|
-
els = await Promise.all(els.map(el => el.jsonValue()))
|
|
2807
|
-
const selected = els.reduce((prev, cur) => prev || cur)
|
|
2808
|
-
return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
|
|
2901
|
+
let els = await findCheckable.call(this, option)
|
|
2902
|
+
assertElementExists(els, option, 'Checkable')
|
|
2903
|
+
els = await Promise.all(els.map((el) => el.getProperty('checked')))
|
|
2904
|
+
els = await Promise.all(els.map((el) => el.jsonValue()))
|
|
2905
|
+
const selected = els.reduce((prev, cur) => prev || cur)
|
|
2906
|
+
return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
|
|
2809
2907
|
}
|
|
2810
2908
|
|
|
2811
2909
|
async function findVisibleFields(locator) {
|
|
2812
|
-
const els = await findFields.call(this, locator)
|
|
2813
|
-
const visible = await Promise.all(els.map(el => el.boundingBox()))
|
|
2814
|
-
return els.filter((el, index) => visible[index])
|
|
2910
|
+
const els = await findFields.call(this, locator)
|
|
2911
|
+
const visible = await Promise.all(els.map((el) => el.boundingBox()))
|
|
2912
|
+
return els.filter((el, index) => visible[index])
|
|
2815
2913
|
}
|
|
2816
2914
|
|
|
2817
2915
|
async function findFields(locator) {
|
|
2818
|
-
const matchedLocator = new Locator(locator)
|
|
2916
|
+
const matchedLocator = new Locator(locator)
|
|
2819
2917
|
if (!matchedLocator.isFuzzy()) {
|
|
2820
|
-
return this._locate(matchedLocator)
|
|
2918
|
+
return this._locate(matchedLocator)
|
|
2821
2919
|
}
|
|
2822
|
-
const literal = xpathLocator.literal(locator)
|
|
2920
|
+
const literal = xpathLocator.literal(locator)
|
|
2823
2921
|
|
|
2824
|
-
let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
|
|
2922
|
+
let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
|
|
2825
2923
|
if (els.length) {
|
|
2826
|
-
return els
|
|
2924
|
+
return els
|
|
2827
2925
|
}
|
|
2828
2926
|
|
|
2829
|
-
els = await this._locate({ xpath: Locator.field.labelContains(literal) })
|
|
2927
|
+
els = await this._locate({ xpath: Locator.field.labelContains(literal) })
|
|
2830
2928
|
if (els.length) {
|
|
2831
|
-
return els
|
|
2929
|
+
return els
|
|
2832
2930
|
}
|
|
2833
|
-
els = await this._locate({ xpath: Locator.field.byName(literal) })
|
|
2931
|
+
els = await this._locate({ xpath: Locator.field.byName(literal) })
|
|
2834
2932
|
if (els.length) {
|
|
2835
|
-
return els
|
|
2933
|
+
return els
|
|
2836
2934
|
}
|
|
2837
|
-
return this._locate({ css: locator })
|
|
2935
|
+
return this._locate({ css: locator })
|
|
2838
2936
|
}
|
|
2839
2937
|
|
|
2840
2938
|
async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
2841
|
-
const src = await this._locate(sourceLocator)
|
|
2842
|
-
assertElementExists(src, sourceLocator, 'Source Element')
|
|
2939
|
+
const src = await this._locate(sourceLocator)
|
|
2940
|
+
assertElementExists(src, sourceLocator, 'Source Element')
|
|
2843
2941
|
|
|
2844
|
-
const dst = await this._locate(destinationLocator)
|
|
2845
|
-
assertElementExists(dst, destinationLocator, 'Destination Element')
|
|
2942
|
+
const dst = await this._locate(destinationLocator)
|
|
2943
|
+
assertElementExists(dst, destinationLocator, 'Destination Element')
|
|
2846
2944
|
|
|
2847
2945
|
// Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
|
|
2848
|
-
const dragSource = await getClickablePoint(src[0])
|
|
2849
|
-
const dragDestination = await getClickablePoint(dst[0])
|
|
2946
|
+
const dragSource = await getClickablePoint(src[0])
|
|
2947
|
+
const dragDestination = await getClickablePoint(dst[0])
|
|
2850
2948
|
|
|
2851
2949
|
// Drag start point
|
|
2852
|
-
await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 })
|
|
2853
|
-
await this.page.mouse.down()
|
|
2950
|
+
await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 })
|
|
2951
|
+
await this.page.mouse.down()
|
|
2854
2952
|
|
|
2855
2953
|
// Drag destination
|
|
2856
|
-
await this.page.mouse.move(dragDestination.x, dragDestination.y, { steps: 5 })
|
|
2857
|
-
await this.page.mouse.up()
|
|
2954
|
+
await this.page.mouse.move(dragDestination.x, dragDestination.y, { steps: 5 })
|
|
2955
|
+
await this.page.mouse.up()
|
|
2858
2956
|
|
|
2859
|
-
await this._waitForAction()
|
|
2957
|
+
await this._waitForAction()
|
|
2860
2958
|
}
|
|
2861
2959
|
|
|
2862
2960
|
async function proceedSeeInField(assertType, field, value) {
|
|
2863
|
-
const els = await findVisibleFields.call(this, field)
|
|
2864
|
-
assertElementExists(els, field, 'Field')
|
|
2865
|
-
const el = els[0]
|
|
2866
|
-
const tag = await el.getProperty('tagName').then(el => el.jsonValue())
|
|
2867
|
-
const fieldType = await el.getProperty('type').then(el => el.jsonValue())
|
|
2961
|
+
const els = await findVisibleFields.call(this, field)
|
|
2962
|
+
assertElementExists(els, field, 'Field')
|
|
2963
|
+
const el = els[0]
|
|
2964
|
+
const tag = await el.getProperty('tagName').then((el) => el.jsonValue())
|
|
2965
|
+
const fieldType = await el.getProperty('type').then((el) => el.jsonValue())
|
|
2868
2966
|
|
|
2869
2967
|
const proceedMultiple = async (elements) => {
|
|
2870
|
-
const fields = Array.isArray(elements) ? elements : [elements]
|
|
2968
|
+
const fields = Array.isArray(elements) ? elements : [elements]
|
|
2871
2969
|
|
|
2872
|
-
const elementValues = []
|
|
2970
|
+
const elementValues = []
|
|
2873
2971
|
for (const element of fields) {
|
|
2874
|
-
elementValues.push(await element.getProperty('value').then(el => el.jsonValue()))
|
|
2972
|
+
elementValues.push(await element.getProperty('value').then((el) => el.jsonValue()))
|
|
2875
2973
|
}
|
|
2876
2974
|
|
|
2877
2975
|
if (typeof value === 'boolean') {
|
|
2878
|
-
equals(`no. of items matching > 0: ${field}`)[assertType](value, !!elementValues.length)
|
|
2976
|
+
equals(`no. of items matching > 0: ${field}`)[assertType](value, !!elementValues.length)
|
|
2879
2977
|
} else {
|
|
2880
2978
|
if (assertType === 'assert') {
|
|
2881
|
-
equals(`select option by ${field}`)[assertType](true, elementValues.length > 0)
|
|
2979
|
+
equals(`select option by ${field}`)[assertType](true, elementValues.length > 0)
|
|
2882
2980
|
}
|
|
2883
|
-
elementValues.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
|
|
2981
|
+
elementValues.forEach((val) => stringIncludes(`fields by ${field}`)[assertType](value, val))
|
|
2884
2982
|
}
|
|
2885
|
-
}
|
|
2983
|
+
}
|
|
2886
2984
|
|
|
2887
2985
|
if (tag === 'SELECT') {
|
|
2888
|
-
const selectedOptions = await el.$$('option:checked')
|
|
2986
|
+
const selectedOptions = await el.$$('option:checked')
|
|
2889
2987
|
// locate option by values and check them
|
|
2890
2988
|
if (value === '') {
|
|
2891
|
-
return proceedMultiple(selectedOptions)
|
|
2989
|
+
return proceedMultiple(selectedOptions)
|
|
2892
2990
|
}
|
|
2893
2991
|
|
|
2894
|
-
const options = await filterFieldsByValue(selectedOptions, value, true)
|
|
2895
|
-
return proceedMultiple(options)
|
|
2992
|
+
const options = await filterFieldsByValue(selectedOptions, value, true)
|
|
2993
|
+
return proceedMultiple(options)
|
|
2896
2994
|
}
|
|
2897
2995
|
|
|
2898
2996
|
if (tag === 'INPUT') {
|
|
2899
2997
|
if (fieldType === 'checkbox' || fieldType === 'radio') {
|
|
2900
2998
|
if (typeof value === 'boolean') {
|
|
2901
2999
|
// Filter by values
|
|
2902
|
-
const options = await filterFieldsBySelectionState(els, true)
|
|
2903
|
-
return proceedMultiple(options)
|
|
3000
|
+
const options = await filterFieldsBySelectionState(els, true)
|
|
3001
|
+
return proceedMultiple(options)
|
|
2904
3002
|
}
|
|
2905
3003
|
|
|
2906
|
-
const options = await filterFieldsByValue(els, value, true)
|
|
2907
|
-
return proceedMultiple(options)
|
|
3004
|
+
const options = await filterFieldsByValue(els, value, true)
|
|
3005
|
+
return proceedMultiple(options)
|
|
2908
3006
|
}
|
|
2909
|
-
return proceedMultiple(els[0])
|
|
3007
|
+
return proceedMultiple(els[0])
|
|
2910
3008
|
}
|
|
2911
|
-
const fieldVal = await el.getProperty('value').then(el => el.jsonValue())
|
|
2912
|
-
return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal)
|
|
3009
|
+
const fieldVal = await el.getProperty('value').then((el) => el.jsonValue())
|
|
3010
|
+
return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal)
|
|
2913
3011
|
}
|
|
2914
3012
|
|
|
2915
3013
|
async function filterFieldsByValue(elements, value, onlySelected) {
|
|
2916
|
-
const matches = []
|
|
3014
|
+
const matches = []
|
|
2917
3015
|
for (const element of elements) {
|
|
2918
|
-
const val = await element.getProperty('value').then(el => el.jsonValue())
|
|
2919
|
-
let isSelected = true
|
|
3016
|
+
const val = await element.getProperty('value').then((el) => el.jsonValue())
|
|
3017
|
+
let isSelected = true
|
|
2920
3018
|
if (onlySelected) {
|
|
2921
|
-
isSelected = await elementSelected(element)
|
|
3019
|
+
isSelected = await elementSelected(element)
|
|
2922
3020
|
}
|
|
2923
3021
|
if ((value == null || val.indexOf(value) > -1) && isSelected) {
|
|
2924
|
-
matches.push(element)
|
|
3022
|
+
matches.push(element)
|
|
2925
3023
|
}
|
|
2926
3024
|
}
|
|
2927
|
-
return matches
|
|
3025
|
+
return matches
|
|
2928
3026
|
}
|
|
2929
3027
|
|
|
2930
3028
|
async function filterFieldsBySelectionState(elements, state) {
|
|
2931
|
-
const matches = []
|
|
3029
|
+
const matches = []
|
|
2932
3030
|
for (const element of elements) {
|
|
2933
|
-
const isSelected = await elementSelected(element)
|
|
3031
|
+
const isSelected = await elementSelected(element)
|
|
2934
3032
|
if (isSelected === state) {
|
|
2935
|
-
matches.push(element)
|
|
3033
|
+
matches.push(element)
|
|
2936
3034
|
}
|
|
2937
3035
|
}
|
|
2938
|
-
return matches
|
|
3036
|
+
return matches
|
|
2939
3037
|
}
|
|
2940
3038
|
|
|
2941
3039
|
async function elementSelected(element) {
|
|
2942
|
-
const type = await element.getProperty('type').then(el => el.jsonValue())
|
|
3040
|
+
const type = await element.getProperty('type').then((el) => el.jsonValue())
|
|
2943
3041
|
|
|
2944
3042
|
if (type === 'checkbox' || type === 'radio') {
|
|
2945
|
-
return element.getProperty('checked').then(el => el.jsonValue())
|
|
3043
|
+
return element.getProperty('checked').then((el) => el.jsonValue())
|
|
2946
3044
|
}
|
|
2947
|
-
return element.getProperty('selected').then(el => el.jsonValue())
|
|
3045
|
+
return element.getProperty('selected').then((el) => el.jsonValue())
|
|
2948
3046
|
}
|
|
2949
3047
|
|
|
2950
3048
|
function isFrameLocator(locator) {
|
|
2951
|
-
locator = new Locator(locator)
|
|
3049
|
+
locator = new Locator(locator)
|
|
2952
3050
|
if (locator.isFrame()) {
|
|
2953
|
-
const _locator = new Locator(locator)
|
|
2954
|
-
return _locator.value
|
|
3051
|
+
const _locator = new Locator(locator)
|
|
3052
|
+
return _locator.value
|
|
2955
3053
|
}
|
|
2956
|
-
return false
|
|
3054
|
+
return false
|
|
2957
3055
|
}
|
|
2958
3056
|
|
|
2959
3057
|
function assertElementExists(res, locator, prefix, suffix) {
|
|
2960
3058
|
if (!res || res.length === 0) {
|
|
2961
|
-
throw new ElementNotFound(locator, prefix, suffix)
|
|
3059
|
+
throw new ElementNotFound(locator, prefix, suffix)
|
|
2962
3060
|
}
|
|
2963
3061
|
}
|
|
2964
3062
|
|
|
2965
3063
|
function $XPath(element, selector) {
|
|
2966
|
-
const found = document.evaluate(selector, element || document.body, null, 5, null)
|
|
2967
|
-
const res = []
|
|
2968
|
-
let current = null
|
|
2969
|
-
while (current = found.iterateNext()) {
|
|
2970
|
-
res.push(current)
|
|
3064
|
+
const found = document.evaluate(selector, element || document.body, null, 5, null)
|
|
3065
|
+
const res = []
|
|
3066
|
+
let current = null
|
|
3067
|
+
while ((current = found.iterateNext())) {
|
|
3068
|
+
res.push(current)
|
|
2971
3069
|
}
|
|
2972
|
-
return res
|
|
3070
|
+
return res
|
|
2973
3071
|
}
|
|
2974
3072
|
|
|
2975
3073
|
async function targetCreatedHandler(page) {
|
|
2976
|
-
if (!page) return
|
|
2977
|
-
this.withinLocator = null
|
|
3074
|
+
if (!page) return
|
|
3075
|
+
this.withinLocator = null
|
|
2978
3076
|
page.on('load', () => {
|
|
2979
|
-
page
|
|
3077
|
+
page
|
|
3078
|
+
.$('body')
|
|
2980
3079
|
.catch(() => null)
|
|
2981
|
-
.then(context => this.context = context)
|
|
2982
|
-
})
|
|
3080
|
+
.then((context) => (this.context = context))
|
|
3081
|
+
})
|
|
2983
3082
|
page.on('console', (msg) => {
|
|
2984
|
-
this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '))
|
|
2985
|
-
consoleLogStore.add(msg)
|
|
2986
|
-
})
|
|
3083
|
+
this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '))
|
|
3084
|
+
consoleLogStore.add(msg)
|
|
3085
|
+
})
|
|
2987
3086
|
|
|
2988
3087
|
if (this.options.userAgent) {
|
|
2989
|
-
await page.setUserAgent(this.options.userAgent)
|
|
3088
|
+
await page.setUserAgent(this.options.userAgent)
|
|
2990
3089
|
}
|
|
2991
3090
|
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
|
|
2992
|
-
const dimensions = this.options.windowSize.split('x')
|
|
2993
|
-
const width = parseInt(dimensions[0], 10)
|
|
2994
|
-
const height = parseInt(dimensions[1], 10)
|
|
2995
|
-
await page.setViewport({ width, height })
|
|
3091
|
+
const dimensions = this.options.windowSize.split('x')
|
|
3092
|
+
const width = parseInt(dimensions[0], 10)
|
|
3093
|
+
const height = parseInt(dimensions[1], 10)
|
|
3094
|
+
await page.setViewport({ width, height })
|
|
2996
3095
|
}
|
|
2997
3096
|
}
|
|
2998
3097
|
|
|
2999
3098
|
// BC compatibility for Puppeteer < 10
|
|
3000
3099
|
async function getClickablePoint(el) {
|
|
3001
|
-
if (el.clickablePoint) return el.clickablePoint()
|
|
3002
|
-
if (el._clickablePoint) return el._clickablePoint()
|
|
3003
|
-
return null
|
|
3100
|
+
if (el.clickablePoint) return el.clickablePoint()
|
|
3101
|
+
if (el._clickablePoint) return el._clickablePoint()
|
|
3102
|
+
return null
|
|
3004
3103
|
}
|
|
3005
3104
|
|
|
3006
3105
|
// List of key values to key definitions
|
|
3007
3106
|
// https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
|
|
3008
3107
|
const keyDefinitionMap = {
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
'z': 'KeyZ',
|
|
3108
|
+
0: 'Digit0',
|
|
3109
|
+
1: 'Digit1',
|
|
3110
|
+
2: 'Digit2',
|
|
3111
|
+
3: 'Digit3',
|
|
3112
|
+
4: 'Digit4',
|
|
3113
|
+
5: 'Digit5',
|
|
3114
|
+
6: 'Digit6',
|
|
3115
|
+
7: 'Digit7',
|
|
3116
|
+
8: 'Digit8',
|
|
3117
|
+
9: 'Digit9',
|
|
3118
|
+
a: 'KeyA',
|
|
3119
|
+
b: 'KeyB',
|
|
3120
|
+
c: 'KeyC',
|
|
3121
|
+
d: 'KeyD',
|
|
3122
|
+
e: 'KeyE',
|
|
3123
|
+
f: 'KeyF',
|
|
3124
|
+
g: 'KeyG',
|
|
3125
|
+
h: 'KeyH',
|
|
3126
|
+
i: 'KeyI',
|
|
3127
|
+
j: 'KeyJ',
|
|
3128
|
+
k: 'KeyK',
|
|
3129
|
+
l: 'KeyL',
|
|
3130
|
+
m: 'KeyM',
|
|
3131
|
+
n: 'KeyN',
|
|
3132
|
+
o: 'KeyO',
|
|
3133
|
+
p: 'KeyP',
|
|
3134
|
+
q: 'KeyQ',
|
|
3135
|
+
r: 'KeyR',
|
|
3136
|
+
s: 'KeyS',
|
|
3137
|
+
t: 'KeyT',
|
|
3138
|
+
u: 'KeyU',
|
|
3139
|
+
v: 'KeyV',
|
|
3140
|
+
w: 'KeyW',
|
|
3141
|
+
x: 'KeyX',
|
|
3142
|
+
y: 'KeyY',
|
|
3143
|
+
z: 'KeyZ',
|
|
3046
3144
|
';': 'Semicolon',
|
|
3047
3145
|
'=': 'Equal',
|
|
3048
3146
|
',': 'Comma',
|
|
@@ -3053,91 +3151,93 @@ const keyDefinitionMap = {
|
|
|
3053
3151
|
'[': 'BracketLeft',
|
|
3054
3152
|
'\\': 'Backslash',
|
|
3055
3153
|
']': 'BracketRight',
|
|
3056
|
-
'
|
|
3057
|
-
|
|
3058
|
-
};
|
|
3154
|
+
"'": 'Quote',
|
|
3155
|
+
}
|
|
3059
3156
|
|
|
3060
3157
|
function getNormalizedKey(key) {
|
|
3061
|
-
const normalizedKey = getNormalizedKeyAttributeValue(key)
|
|
3158
|
+
const normalizedKey = getNormalizedKeyAttributeValue(key)
|
|
3062
3159
|
if (key !== normalizedKey) {
|
|
3063
|
-
this.debugSection('Input', `Mapping key '${key}' to '${normalizedKey}'`)
|
|
3160
|
+
this.debugSection('Input', `Mapping key '${key}' to '${normalizedKey}'`)
|
|
3064
3161
|
}
|
|
3065
3162
|
// Use key definition to ensure correct key is displayed when Shift modifier is active
|
|
3066
3163
|
if (Object.prototype.hasOwnProperty.call(keyDefinitionMap, normalizedKey)) {
|
|
3067
|
-
return keyDefinitionMap[normalizedKey]
|
|
3164
|
+
return keyDefinitionMap[normalizedKey]
|
|
3068
3165
|
}
|
|
3069
|
-
return normalizedKey
|
|
3166
|
+
return normalizedKey
|
|
3070
3167
|
}
|
|
3071
3168
|
|
|
3072
3169
|
function highlightActiveElement(element, context) {
|
|
3073
3170
|
if (this.options.highlightElement && global.debugMode) {
|
|
3074
|
-
highlightElement(element, context)
|
|
3171
|
+
highlightElement(element, context)
|
|
3075
3172
|
}
|
|
3076
3173
|
}
|
|
3077
3174
|
|
|
3078
3175
|
function _waitForElement(locator, options) {
|
|
3079
3176
|
try {
|
|
3080
|
-
return this.context.waitForXPath(locator.value, options)
|
|
3177
|
+
return this.context.waitForXPath(locator.value, options)
|
|
3081
3178
|
} catch (e) {
|
|
3082
|
-
return this.context.waitForSelector(`::-p-xpath(${locator.value})`, options)
|
|
3179
|
+
return this.context.waitForSelector(`::-p-xpath(${locator.value})`, options)
|
|
3083
3180
|
}
|
|
3084
3181
|
}
|
|
3085
3182
|
|
|
3086
3183
|
async function findReactElements(locator, props = {}, state = {}) {
|
|
3087
|
-
const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8')
|
|
3088
|
-
await this.page.evaluate(resqScript.toString())
|
|
3089
|
-
|
|
3090
|
-
await this.page.evaluate(() => window.resq.waitToLoadReact())
|
|
3091
|
-
const arrayHandle = await this.page.evaluateHandle(
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
if (!elements.length) {
|
|
3102
|
-
return [];
|
|
3103
|
-
}
|
|
3104
|
-
|
|
3105
|
-
// resq returns an array of HTMLElements if the React component is a fragment
|
|
3106
|
-
// this avoids having nested arrays of nodes which the driver does not understand
|
|
3107
|
-
// [[div, div], [div, div]] => [div, div, div, div]
|
|
3108
|
-
let nodes = [];
|
|
3109
|
-
|
|
3110
|
-
elements.forEach((element) => {
|
|
3111
|
-
let { node, isFragment } = element;
|
|
3112
|
-
|
|
3113
|
-
if (!node) {
|
|
3114
|
-
isFragment = true;
|
|
3115
|
-
node = element.children;
|
|
3184
|
+
const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8')
|
|
3185
|
+
await this.page.evaluate(resqScript.toString())
|
|
3186
|
+
|
|
3187
|
+
await this.page.evaluate(() => window.resq.waitToLoadReact())
|
|
3188
|
+
const arrayHandle = await this.page.evaluateHandle(
|
|
3189
|
+
(obj) => {
|
|
3190
|
+
const { selector, props, state } = obj
|
|
3191
|
+
let elements = window.resq.resq$$(selector)
|
|
3192
|
+
if (Object.keys(props).length) {
|
|
3193
|
+
elements = elements.byProps(props)
|
|
3194
|
+
}
|
|
3195
|
+
if (Object.keys(state).length) {
|
|
3196
|
+
elements = elements.byState(state)
|
|
3116
3197
|
}
|
|
3117
3198
|
|
|
3118
|
-
if (
|
|
3119
|
-
|
|
3120
|
-
} else {
|
|
3121
|
-
nodes.push(node);
|
|
3199
|
+
if (!elements.length) {
|
|
3200
|
+
return []
|
|
3122
3201
|
}
|
|
3123
|
-
});
|
|
3124
3202
|
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
state: locator.state || {},
|
|
3130
|
-
});
|
|
3203
|
+
// resq returns an array of HTMLElements if the React component is a fragment
|
|
3204
|
+
// this avoids having nested arrays of nodes which the driver does not understand
|
|
3205
|
+
// [[div, div], [div, div]] => [div, div, div, div]
|
|
3206
|
+
let nodes = []
|
|
3131
3207
|
|
|
3132
|
-
|
|
3133
|
-
|
|
3208
|
+
elements.forEach((element) => {
|
|
3209
|
+
let { node, isFragment } = element
|
|
3210
|
+
|
|
3211
|
+
if (!node) {
|
|
3212
|
+
isFragment = true
|
|
3213
|
+
node = element.children
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
if (isFragment) {
|
|
3217
|
+
nodes = nodes.concat(node)
|
|
3218
|
+
} else {
|
|
3219
|
+
nodes.push(node)
|
|
3220
|
+
}
|
|
3221
|
+
})
|
|
3222
|
+
|
|
3223
|
+
return [...nodes]
|
|
3224
|
+
},
|
|
3225
|
+
{
|
|
3226
|
+
selector: locator.react,
|
|
3227
|
+
props: locator.props || {},
|
|
3228
|
+
state: locator.state || {},
|
|
3229
|
+
},
|
|
3230
|
+
)
|
|
3231
|
+
|
|
3232
|
+
const properties = await arrayHandle.getProperties()
|
|
3233
|
+
const result = []
|
|
3134
3234
|
for (const property of properties.values()) {
|
|
3135
|
-
const elementHandle = property.asElement()
|
|
3235
|
+
const elementHandle = property.asElement()
|
|
3136
3236
|
if (elementHandle) {
|
|
3137
|
-
result.push(elementHandle)
|
|
3237
|
+
result.push(elementHandle)
|
|
3138
3238
|
}
|
|
3139
3239
|
}
|
|
3140
3240
|
|
|
3141
|
-
await arrayHandle.dispose()
|
|
3142
|
-
return result
|
|
3241
|
+
await arrayHandle.dispose()
|
|
3242
|
+
return result
|
|
3143
3243
|
}
|