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