codeceptjs 4.0.0-beta.5 → 4.0.0-beta.6.esm-aria
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -45
- package/bin/codecept.js +46 -57
- package/lib/actor.js +15 -11
- package/lib/ai.js +6 -5
- package/lib/assert/empty.js +9 -8
- package/lib/assert/equal.js +15 -17
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +9 -11
- package/lib/assert/throws.js +1 -1
- package/lib/assert/truth.js +8 -5
- package/lib/assert.js +18 -18
- package/lib/codecept.js +66 -107
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +32 -27
- package/lib/command/configMigrate.js +11 -10
- package/lib/command/definitions.js +16 -10
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +29 -26
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +14 -14
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +8 -8
- package/lib/command/init.js +34 -31
- package/lib/command/interactive.js +11 -10
- package/lib/command/list.js +10 -9
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +16 -13
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +10 -24
- package/lib/command/run.js +8 -8
- package/lib/command/utils.js +20 -18
- package/lib/command/workers/runTests.js +117 -269
- package/lib/config.js +111 -49
- package/lib/container.js +299 -102
- package/lib/data/context.js +6 -5
- package/lib/data/dataScenarioConfig.js +1 -1
- package/lib/data/dataTableArgument.js +1 -1
- package/lib/data/table.js +1 -1
- package/lib/effects.js +94 -10
- package/lib/els.js +11 -9
- package/lib/event.js +11 -10
- package/lib/globals.js +141 -0
- package/lib/heal.js +12 -12
- package/lib/helper/AI.js +1 -1
- package/lib/helper/ApiDataFactory.js +16 -13
- package/lib/helper/FileSystem.js +32 -12
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +1 -1
- package/lib/helper/JSONResponse.js +19 -30
- package/lib/helper/Mochawesome.js +9 -28
- package/lib/helper/Playwright.js +668 -265
- package/lib/helper/Puppeteer.js +284 -169
- package/lib/helper/REST.js +29 -12
- package/lib/helper/WebDriver.js +191 -71
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +33 -48
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +8 -9
- package/lib/listener/emptyRun.js +6 -7
- package/lib/listener/exit.js +4 -3
- package/lib/listener/globalRetry.js +5 -5
- package/lib/listener/globalTimeout.js +11 -10
- package/lib/listener/helpers.js +33 -14
- package/lib/listener/mocha.js +3 -4
- package/lib/listener/result.js +4 -5
- package/lib/listener/steps.js +7 -18
- package/lib/listener/store.js +3 -3
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +108 -75
- package/lib/mocha/bdd.js +99 -13
- package/lib/mocha/cli.js +60 -27
- package/lib/mocha/factory.js +75 -19
- package/lib/mocha/featureConfig.js +1 -1
- package/lib/mocha/gherkin.js +57 -25
- package/lib/mocha/hooks.js +12 -3
- package/lib/mocha/index.js +13 -4
- package/lib/mocha/inject.js +22 -5
- package/lib/mocha/scenarioConfig.js +2 -2
- package/lib/mocha/suite.js +9 -2
- package/lib/mocha/test.js +10 -13
- package/lib/mocha/ui.js +28 -31
- package/lib/output.js +11 -9
- package/lib/parser.js +44 -44
- package/lib/pause.js +15 -16
- package/lib/plugin/analyze.js +19 -12
- package/lib/plugin/auth.js +20 -21
- package/lib/plugin/autoDelay.js +12 -8
- package/lib/plugin/coverage.js +12 -8
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +3 -2
- package/lib/plugin/heal.js +14 -9
- package/lib/plugin/pageInfo.js +10 -10
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +47 -5
- package/lib/plugin/screenshotOnFail.js +75 -37
- package/lib/plugin/stepByStepReport.js +14 -14
- package/lib/plugin/stepTimeout.js +4 -3
- package/lib/plugin/subtitles.js +6 -5
- package/lib/recorder.js +33 -23
- package/lib/rerun.js +69 -26
- package/lib/result.js +4 -4
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- package/lib/step/base.js +6 -6
- package/lib/step/config.js +1 -1
- package/lib/step/func.js +3 -3
- package/lib/step/helper.js +3 -3
- package/lib/step/meta.js +4 -4
- package/lib/step/record.js +11 -11
- package/lib/step/retry.js +3 -3
- package/lib/step/section.js +3 -3
- package/lib/step.js +7 -10
- package/lib/steps.js +9 -5
- package/lib/store.js +1 -1
- package/lib/timeout.js +1 -7
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils.js +68 -97
- package/lib/workerStorage.js +16 -17
- package/lib/workers.js +145 -171
- package/package.json +63 -57
- package/translations/de-DE.js +2 -2
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +23 -10
- package/translations/it-IT.js +2 -2
- package/translations/ja-JP.js +2 -2
- package/translations/nl-NL.js +2 -2
- package/translations/pl-PL.js +2 -2
- package/translations/pt-BR.js +2 -2
- package/translations/ru-RU.js +2 -2
- package/translations/utils.js +11 -2
- package/translations/zh-CN.js +2 -2
- package/translations/zh-TW.js +2 -2
- package/typings/index.d.ts +7 -18
- package/typings/promiseBasedTypes.d.ts +3769 -5450
- package/typings/types.d.ts +3953 -5778
- package/bin/test-server.js +0 -53
- package/lib/element/WebElement.js +0 -327
- package/lib/helper/Nightmare.js +0 -1486
- package/lib/helper/Protractor.js +0 -1840
- package/lib/helper/TestCafe.js +0 -1391
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -61
- package/lib/listener/retryEnhancer.js +0 -85
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/commentStep.js +0 -141
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/htmlReporter.js +0 -1947
- package/lib/plugin/retryTo.js +0 -16
- package/lib/plugin/selenoid.js +0 -364
- package/lib/plugin/standardActingHelpers.js +0 -6
- package/lib/plugin/tryTo.js +0 -16
- package/lib/plugin/wdio.js +0 -247
- package/lib/test-server.js +0 -323
- package/lib/within.js +0 -90
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const isElementClickable = require('./scripts/isElementClickable')
|
|
18
|
-
const {
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import fsExtra from 'fs-extra'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import Helper from '@codeceptjs/helper'
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
7
|
+
import promiseRetry from 'promise-retry'
|
|
8
|
+
import Locator from '../locator.js'
|
|
9
|
+
import recorder from '../recorder.js'
|
|
10
|
+
import store from '../store.js'
|
|
11
|
+
import { includes as stringIncludes } from '../assert/include.js'
|
|
12
|
+
import { urlEquals, equals } from '../assert/equal.js'
|
|
13
|
+
import { empty } from '../assert/empty.js'
|
|
14
|
+
import { truth } from '../assert/truth.js'
|
|
15
|
+
import isElementClickable from './scripts/isElementClickable.js'
|
|
16
|
+
import {
|
|
19
17
|
xpathLocator,
|
|
20
18
|
ucfirst,
|
|
21
19
|
fileExists,
|
|
@@ -28,19 +26,31 @@ const {
|
|
|
28
26
|
isModifierKey,
|
|
29
27
|
requireWithFallback,
|
|
30
28
|
normalizeSpacesInString,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const WebElement = require('../element/WebElement')
|
|
29
|
+
} from '../utils.js'
|
|
30
|
+
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
|
|
31
|
+
import ElementNotFound from './errors/ElementNotFound.js'
|
|
32
|
+
import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
|
|
33
|
+
import Popup from './extras/Popup.js'
|
|
34
|
+
import Console from './extras/Console.js'
|
|
35
|
+
import { highlightElement } from './scripts/highlightElement.js'
|
|
36
|
+
import { blurElement } from './scripts/blurElement.js'
|
|
37
|
+
import { dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError } from './errors/ElementAssertion.js'
|
|
38
|
+
import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
|
|
42
39
|
|
|
43
40
|
let puppeteer
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Wraps error objects that don't have a proper message property
|
|
44
|
+
* This is needed for ESM compatibility with Puppeteer error handling
|
|
45
|
+
*/
|
|
46
|
+
function wrapError(e) {
|
|
47
|
+
if (e && typeof e === 'object' && !e.message) {
|
|
48
|
+
const err = new Error(String(e))
|
|
49
|
+
err.stack = e.stack
|
|
50
|
+
return err
|
|
51
|
+
}
|
|
52
|
+
return e
|
|
53
|
+
}
|
|
44
54
|
let perfTiming
|
|
45
55
|
const popupStore = new Popup()
|
|
46
56
|
const consoleLogStore = new Console()
|
|
@@ -214,7 +224,7 @@ class Puppeteer extends Helper {
|
|
|
214
224
|
constructor(config) {
|
|
215
225
|
super(config)
|
|
216
226
|
|
|
217
|
-
puppeteer
|
|
227
|
+
// puppeteer will be loaded dynamically in _init method
|
|
218
228
|
// set defaults
|
|
219
229
|
this.isRemoteBrowser = false
|
|
220
230
|
this.isRunning = false
|
|
@@ -294,13 +304,34 @@ class Puppeteer extends Helper {
|
|
|
294
304
|
|
|
295
305
|
static _checkRequirements() {
|
|
296
306
|
try {
|
|
297
|
-
|
|
307
|
+
// In ESM, puppeteer will be checked via dynamic import in _init
|
|
308
|
+
// The import will fail at module load time if puppeteer is missing
|
|
309
|
+
return null
|
|
298
310
|
} catch (e) {
|
|
299
311
|
return ['puppeteer']
|
|
300
312
|
}
|
|
301
313
|
}
|
|
302
314
|
|
|
303
|
-
_init() {
|
|
315
|
+
async _init() {
|
|
316
|
+
// Load puppeteer dynamically with fallback
|
|
317
|
+
if (!puppeteer) {
|
|
318
|
+
try {
|
|
319
|
+
const puppeteerModule = await import('puppeteer')
|
|
320
|
+
puppeteer = puppeteerModule.default || puppeteerModule
|
|
321
|
+
this.debugSection('Puppeteer', `Loaded puppeteer successfully, launch available: ${!!puppeteer.launch}`)
|
|
322
|
+
} catch (e) {
|
|
323
|
+
try {
|
|
324
|
+
const puppeteerModule = await import('puppeteer-core')
|
|
325
|
+
puppeteer = puppeteerModule.default || puppeteerModule
|
|
326
|
+
this.debugSection('Puppeteer', `Loaded puppeteer-core successfully, launch available: ${!!puppeteer.launch}`)
|
|
327
|
+
} catch (e2) {
|
|
328
|
+
throw new Error('Neither puppeteer nor puppeteer-core could be loaded. Please install one of them.')
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
this.debugSection('Puppeteer', `Puppeteer already loaded, launch available: ${!!puppeteer.launch}`)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
304
335
|
|
|
305
336
|
_beforeSuite() {
|
|
306
337
|
if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
|
|
@@ -563,6 +594,12 @@ class Puppeteer extends Helper {
|
|
|
563
594
|
}
|
|
564
595
|
|
|
565
596
|
async _startBrowser() {
|
|
597
|
+
this.debugSection('Puppeteer', `Starting browser. Puppeteer available: ${!!puppeteer}, launch available: ${!!puppeteer?.launch}`)
|
|
598
|
+
|
|
599
|
+
if (!puppeteer) {
|
|
600
|
+
throw new Error('Puppeteer is not loaded. Make sure _init() was called before _startBrowser()')
|
|
601
|
+
}
|
|
602
|
+
|
|
566
603
|
if (this.isRemoteBrowser) {
|
|
567
604
|
try {
|
|
568
605
|
this.browser = await puppeteer.connect(this.puppeteerOptions)
|
|
@@ -634,11 +671,9 @@ class Puppeteer extends Helper {
|
|
|
634
671
|
return
|
|
635
672
|
}
|
|
636
673
|
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
this.context = el
|
|
674
|
+
const els = await this._locate(locator)
|
|
675
|
+
assertElementExists(els, locator)
|
|
676
|
+
this.context = els[0]
|
|
642
677
|
|
|
643
678
|
this.withinLocator = new Locator(locator)
|
|
644
679
|
}
|
|
@@ -682,7 +717,21 @@ class Puppeteer extends Helper {
|
|
|
682
717
|
this.currentRunningTest.artifacts.trace = fileName
|
|
683
718
|
}
|
|
684
719
|
|
|
685
|
-
|
|
720
|
+
try {
|
|
721
|
+
await this.page.goto(url, { waitUntil: this.options.waitForNavigation })
|
|
722
|
+
} catch (err) {
|
|
723
|
+
// Handle terminal navigation errors that shouldn't be retried
|
|
724
|
+
if (
|
|
725
|
+
err.message &&
|
|
726
|
+
(err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed') || err.message.includes('Navigation timeout'))
|
|
727
|
+
) {
|
|
728
|
+
// Mark this as a terminal error to prevent retries
|
|
729
|
+
const terminalError = new Error(err.message)
|
|
730
|
+
terminalError.isTerminal = true
|
|
731
|
+
throw terminalError
|
|
732
|
+
}
|
|
733
|
+
throw err
|
|
734
|
+
}
|
|
686
735
|
|
|
687
736
|
const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)))
|
|
688
737
|
|
|
@@ -732,13 +781,11 @@ class Puppeteer extends Helper {
|
|
|
732
781
|
* {{ react }}
|
|
733
782
|
*/
|
|
734
783
|
async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
throw new ElementNotFound(locator, 'Element to move cursor to')
|
|
738
|
-
}
|
|
784
|
+
const els = await this._locate(locator)
|
|
785
|
+
assertElementExists(els, locator)
|
|
739
786
|
|
|
740
787
|
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
|
|
741
|
-
const { x, y } = await getClickablePoint(
|
|
788
|
+
const { x, y } = await getClickablePoint(els[0])
|
|
742
789
|
await this.page.mouse.move(x + offsetX, y + offsetY)
|
|
743
790
|
return this._waitForAction()
|
|
744
791
|
}
|
|
@@ -748,10 +795,9 @@ class Puppeteer extends Helper {
|
|
|
748
795
|
*
|
|
749
796
|
*/
|
|
750
797
|
async focus(locator) {
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
798
|
+
const els = await this._locate(locator)
|
|
799
|
+
assertElementExists(els, locator, 'Element to focus')
|
|
800
|
+
const el = els[0]
|
|
755
801
|
|
|
756
802
|
await el.click()
|
|
757
803
|
await el.focus()
|
|
@@ -763,12 +809,10 @@ class Puppeteer extends Helper {
|
|
|
763
809
|
*
|
|
764
810
|
*/
|
|
765
811
|
async blur(locator) {
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
throw new ElementNotFound(locator, 'Element to blur')
|
|
769
|
-
}
|
|
812
|
+
const els = await this._locate(locator)
|
|
813
|
+
assertElementExists(els, locator, 'Element to blur')
|
|
770
814
|
|
|
771
|
-
await blurElement(
|
|
815
|
+
await blurElement(els[0], this.page)
|
|
772
816
|
return this._waitForAction()
|
|
773
817
|
}
|
|
774
818
|
|
|
@@ -817,12 +861,11 @@ class Puppeteer extends Helper {
|
|
|
817
861
|
}
|
|
818
862
|
|
|
819
863
|
if (locator) {
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
}
|
|
864
|
+
const els = await this._locate(locator)
|
|
865
|
+
assertElementExists(els, locator, 'Element')
|
|
866
|
+
const el = els[0]
|
|
824
867
|
await el.evaluate(el => el.scrollIntoView())
|
|
825
|
-
const elementCoordinates = await getClickablePoint(
|
|
868
|
+
const elementCoordinates = await getClickablePoint(els[0])
|
|
826
869
|
await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY)
|
|
827
870
|
} else {
|
|
828
871
|
await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY)
|
|
@@ -890,21 +933,6 @@ class Puppeteer extends Helper {
|
|
|
890
933
|
return findElements.call(this, context, locator)
|
|
891
934
|
}
|
|
892
935
|
|
|
893
|
-
/**
|
|
894
|
-
* Get single element by different locator types, including strict locator
|
|
895
|
-
* Should be used in custom helpers:
|
|
896
|
-
*
|
|
897
|
-
* ```js
|
|
898
|
-
* const element = await this.helpers['Puppeteer']._locateElement({name: 'password'});
|
|
899
|
-
* ```
|
|
900
|
-
*
|
|
901
|
-
* {{ react }}
|
|
902
|
-
*/
|
|
903
|
-
async _locateElement(locator) {
|
|
904
|
-
const context = await this.context
|
|
905
|
-
return findElement.call(this, context, locator)
|
|
906
|
-
}
|
|
907
|
-
|
|
908
936
|
/**
|
|
909
937
|
* Find a checkbox by providing human-readable text:
|
|
910
938
|
* NOTE: Assumes the checkable element exists
|
|
@@ -916,9 +944,7 @@ class Puppeteer extends Helper {
|
|
|
916
944
|
async _locateCheckable(locator, providedContext = null) {
|
|
917
945
|
const context = providedContext || (await this._getContext())
|
|
918
946
|
const els = await findCheckable.call(this, locator, context)
|
|
919
|
-
|
|
920
|
-
throw new ElementNotFound(locator, 'Checkbox or radio')
|
|
921
|
-
}
|
|
947
|
+
assertElementExists(els[0], locator, 'Checkbox or radio')
|
|
922
948
|
return els[0]
|
|
923
949
|
}
|
|
924
950
|
|
|
@@ -950,20 +976,13 @@ class Puppeteer extends Helper {
|
|
|
950
976
|
*
|
|
951
977
|
*/
|
|
952
978
|
async grabWebElements(locator) {
|
|
953
|
-
|
|
954
|
-
return elements.map(element => new WebElement(element, this))
|
|
979
|
+
return this._locate(locator)
|
|
955
980
|
}
|
|
956
981
|
|
|
957
|
-
/**
|
|
958
|
-
* {{> grabWebElement }}
|
|
959
|
-
*
|
|
960
|
-
*/
|
|
961
982
|
async grabWebElement(locator) {
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}
|
|
966
|
-
return new WebElement(elements[0], this)
|
|
983
|
+
const els = await this._locate(locator)
|
|
984
|
+
assertElementExists(els, locator)
|
|
985
|
+
return els[0]
|
|
967
986
|
}
|
|
968
987
|
|
|
969
988
|
/**
|
|
@@ -1292,8 +1311,16 @@ class Puppeteer extends Helper {
|
|
|
1292
1311
|
*/
|
|
1293
1312
|
async checkOption(field, context = null) {
|
|
1294
1313
|
const elm = await this._locateCheckable(field, context)
|
|
1295
|
-
|
|
1296
|
-
|
|
1314
|
+
let curentlyChecked = await elm
|
|
1315
|
+
.getProperty('checked')
|
|
1316
|
+
.then(checkedProperty => checkedProperty.jsonValue())
|
|
1317
|
+
.catch(() => null)
|
|
1318
|
+
|
|
1319
|
+
if (!curentlyChecked) {
|
|
1320
|
+
const ariaChecked = await elm.evaluate(el => el.getAttribute('aria-checked'))
|
|
1321
|
+
curentlyChecked = ariaChecked === 'true'
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1297
1324
|
if (!curentlyChecked) {
|
|
1298
1325
|
await elm.click()
|
|
1299
1326
|
return this._waitForAction()
|
|
@@ -1305,8 +1332,16 @@ class Puppeteer extends Helper {
|
|
|
1305
1332
|
*/
|
|
1306
1333
|
async uncheckOption(field, context = null) {
|
|
1307
1334
|
const elm = await this._locateCheckable(field, context)
|
|
1308
|
-
|
|
1309
|
-
|
|
1335
|
+
let curentlyChecked = await elm
|
|
1336
|
+
.getProperty('checked')
|
|
1337
|
+
.then(checkedProperty => checkedProperty.jsonValue())
|
|
1338
|
+
.catch(() => null)
|
|
1339
|
+
|
|
1340
|
+
if (!curentlyChecked) {
|
|
1341
|
+
const ariaChecked = await elm.evaluate(el => el.getAttribute('aria-checked'))
|
|
1342
|
+
curentlyChecked = ariaChecked === 'true'
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1310
1345
|
if (curentlyChecked) {
|
|
1311
1346
|
await elm.click()
|
|
1312
1347
|
return this._waitForAction()
|
|
@@ -2149,13 +2184,12 @@ class Puppeteer extends Helper {
|
|
|
2149
2184
|
* {{> waitForClickable }}
|
|
2150
2185
|
*/
|
|
2151
2186
|
async waitForClickable(locator, waitTimeout) {
|
|
2152
|
-
const
|
|
2153
|
-
|
|
2154
|
-
throw new ElementNotFound(locator, 'Element to wait for clickable')
|
|
2155
|
-
}
|
|
2187
|
+
const els = await this._locate(locator)
|
|
2188
|
+
assertElementExists(els, locator)
|
|
2156
2189
|
|
|
2157
|
-
return this.waitForFunction(isElementClickable, [
|
|
2158
|
-
|
|
2190
|
+
return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async e => {
|
|
2191
|
+
const errorMessage = e?.message || String(e)
|
|
2192
|
+
if (/Waiting failed/i.test(errorMessage) || /failed: timeout/i.test(errorMessage)) {
|
|
2159
2193
|
throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`)
|
|
2160
2194
|
} else {
|
|
2161
2195
|
throw e
|
|
@@ -2726,51 +2760,27 @@ class Puppeteer extends Helper {
|
|
|
2726
2760
|
}
|
|
2727
2761
|
}
|
|
2728
2762
|
|
|
2729
|
-
module.exports = Puppeteer
|
|
2730
|
-
|
|
2731
|
-
/**
|
|
2732
|
-
* Find elements using Puppeteer's native element discovery methods
|
|
2733
|
-
* Note: Unlike Playwright, Puppeteer's Locator API doesn't have .all() method for multiple elements
|
|
2734
|
-
* @param {Object} matcher - Puppeteer context to search within
|
|
2735
|
-
* @param {Object|string} locator - Locator specification
|
|
2736
|
-
* @returns {Promise<Array>} Array of ElementHandle objects
|
|
2737
|
-
*/
|
|
2738
2763
|
async function findElements(matcher, locator) {
|
|
2739
|
-
|
|
2740
|
-
locator = new Locator(locator, 'css')
|
|
2764
|
+
const matchedLocator = new Locator(locator, 'css')
|
|
2741
2765
|
|
|
2742
|
-
|
|
2743
|
-
if (
|
|
2744
|
-
// puppeteer version < 19.4.0 is no longer supported. This one is backward support.
|
|
2745
|
-
if (puppeteer.default?.defaultBrowserRevision) {
|
|
2746
|
-
return matcher.$$(`xpath/${locator.value}`)
|
|
2747
|
-
}
|
|
2748
|
-
return matcher.$x(locator.value)
|
|
2749
|
-
}
|
|
2766
|
+
if (matchedLocator.type === 'react') return findReactElements.call(this, matchedLocator)
|
|
2767
|
+
if (matchedLocator.isRole()) return findByRole.call(this, matcher, matchedLocator)
|
|
2750
2768
|
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
}
|
|
2767
|
-
// puppeteer version < 19.4.0 is no longer supported. This one is backward support.
|
|
2768
|
-
if (puppeteer.default?.defaultBrowserRevision) {
|
|
2769
|
-
const elements = await matcher.$$(`xpath/${locator.value}`)
|
|
2770
|
-
return elements[0]
|
|
2771
|
-
}
|
|
2772
|
-
const elements = await matcher.$x(locator.value)
|
|
2773
|
-
return elements[0]
|
|
2769
|
+
if (!matchedLocator.isXPath()) return matcher.$$(matchedLocator.simplify())
|
|
2770
|
+
|
|
2771
|
+
// Handle backward compatibility for different Puppeteer versions
|
|
2772
|
+
// Puppeteer >= 19.4.0 uses xpath/ syntax, older versions use $x
|
|
2773
|
+
try {
|
|
2774
|
+
// Try the new xpath syntax first (for Puppeteer >= 19.4.0)
|
|
2775
|
+
return await matcher.$$(`xpath/${matchedLocator.value}`)
|
|
2776
|
+
} catch (error) {
|
|
2777
|
+
// Fall back to the old $x method for older Puppeteer versions
|
|
2778
|
+
if (matcher.$x && typeof matcher.$x === 'function') {
|
|
2779
|
+
return await matcher.$x(matchedLocator.value)
|
|
2780
|
+
}
|
|
2781
|
+
// If both methods fail, re-throw the original error
|
|
2782
|
+
throw error
|
|
2783
|
+
}
|
|
2774
2784
|
}
|
|
2775
2785
|
|
|
2776
2786
|
async function proceedClick(locator, context = null, options = {}) {
|
|
@@ -2800,12 +2810,12 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2800
2810
|
}
|
|
2801
2811
|
|
|
2802
2812
|
async function findClickable(matcher, locator) {
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
if (!
|
|
2813
|
+
const matchedLocator = new Locator(locator)
|
|
2814
|
+
|
|
2815
|
+
if (!matchedLocator.isFuzzy()) return findElements.call(this, matcher, matchedLocator)
|
|
2806
2816
|
|
|
2807
2817
|
let els
|
|
2808
|
-
const literal = xpathLocator.literal(
|
|
2818
|
+
const literal = xpathLocator.literal(matchedLocator.value)
|
|
2809
2819
|
|
|
2810
2820
|
els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
|
|
2811
2821
|
if (els.length) return els
|
|
@@ -2820,7 +2830,15 @@ async function findClickable(matcher, locator) {
|
|
|
2820
2830
|
// Do nothing
|
|
2821
2831
|
}
|
|
2822
2832
|
|
|
2823
|
-
|
|
2833
|
+
// Try ARIA selector for accessible name
|
|
2834
|
+
try {
|
|
2835
|
+
els = await matcher.$$(`::-p-aria(${matchedLocator.value})`)
|
|
2836
|
+
if (els.length) return els
|
|
2837
|
+
} catch (err) {
|
|
2838
|
+
// ARIA selector not supported or failed
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
return findElements.call(this, matcher, matchedLocator.value) // by css or xpath
|
|
2824
2842
|
}
|
|
2825
2843
|
|
|
2826
2844
|
async function proceedSee(assertType, text, context, strict = false) {
|
|
@@ -2864,10 +2882,10 @@ async function findCheckable(locator, context) {
|
|
|
2864
2882
|
|
|
2865
2883
|
const matchedLocator = new Locator(locator)
|
|
2866
2884
|
if (!matchedLocator.isFuzzy()) {
|
|
2867
|
-
return findElements.call(this, contextEl, matchedLocator
|
|
2885
|
+
return findElements.call(this, contextEl, matchedLocator)
|
|
2868
2886
|
}
|
|
2869
2887
|
|
|
2870
|
-
const literal = xpathLocator.literal(
|
|
2888
|
+
const literal = xpathLocator.literal(matchedLocator.value)
|
|
2871
2889
|
let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal))
|
|
2872
2890
|
if (els.length) {
|
|
2873
2891
|
return els
|
|
@@ -2876,15 +2894,39 @@ async function findCheckable(locator, context) {
|
|
|
2876
2894
|
if (els.length) {
|
|
2877
2895
|
return els
|
|
2878
2896
|
}
|
|
2879
|
-
|
|
2897
|
+
|
|
2898
|
+
// Try ARIA selector for accessible name
|
|
2899
|
+
try {
|
|
2900
|
+
els = await contextEl.$$(`::-p-aria(${matchedLocator.value})`)
|
|
2901
|
+
if (els.length) return els
|
|
2902
|
+
} catch (err) {
|
|
2903
|
+
// ARIA selector not supported or failed
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
return findElements.call(this, contextEl, matchedLocator.value)
|
|
2880
2907
|
}
|
|
2881
2908
|
|
|
2882
2909
|
async function proceedIsChecked(assertType, option) {
|
|
2883
2910
|
let els = await findCheckable.call(this, option)
|
|
2884
2911
|
assertElementExists(els, option, 'Checkable')
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2912
|
+
|
|
2913
|
+
const checkedStates = await Promise.all(
|
|
2914
|
+
els.map(async el => {
|
|
2915
|
+
const checked = await el
|
|
2916
|
+
.getProperty('checked')
|
|
2917
|
+
.then(p => p.jsonValue())
|
|
2918
|
+
.catch(() => null)
|
|
2919
|
+
|
|
2920
|
+
if (checked) {
|
|
2921
|
+
return checked
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
const ariaChecked = await el.evaluate(el => el.getAttribute('aria-checked'))
|
|
2925
|
+
return ariaChecked === 'true'
|
|
2926
|
+
}),
|
|
2927
|
+
)
|
|
2928
|
+
|
|
2929
|
+
const selected = checkedStates.reduce((prev, cur) => prev || cur)
|
|
2888
2930
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
|
|
2889
2931
|
}
|
|
2890
2932
|
|
|
@@ -2899,7 +2941,7 @@ async function findFields(locator) {
|
|
|
2899
2941
|
if (!matchedLocator.isFuzzy()) {
|
|
2900
2942
|
return this._locate(matchedLocator)
|
|
2901
2943
|
}
|
|
2902
|
-
const literal = xpathLocator.literal(
|
|
2944
|
+
const literal = xpathLocator.literal(matchedLocator.value)
|
|
2903
2945
|
|
|
2904
2946
|
let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
|
|
2905
2947
|
if (els.length) {
|
|
@@ -2914,23 +2956,29 @@ async function findFields(locator) {
|
|
|
2914
2956
|
if (els.length) {
|
|
2915
2957
|
return els
|
|
2916
2958
|
}
|
|
2917
|
-
|
|
2959
|
+
|
|
2960
|
+
// Try ARIA selector for accessible name
|
|
2961
|
+
try {
|
|
2962
|
+
const page = await this.context
|
|
2963
|
+
els = await page.$$(`::-p-aria(${matchedLocator.value})`)
|
|
2964
|
+
if (els.length) return els
|
|
2965
|
+
} catch (err) {
|
|
2966
|
+
// ARIA selector not supported or failed
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
return this._locate({ css: matchedLocator.value })
|
|
2918
2970
|
}
|
|
2919
2971
|
|
|
2920
2972
|
async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
2921
|
-
const src = await this.
|
|
2922
|
-
|
|
2923
|
-
throw new ElementNotFound(sourceLocator, 'Source Element')
|
|
2924
|
-
}
|
|
2973
|
+
const src = await this._locate(sourceLocator)
|
|
2974
|
+
assertElementExists(src, sourceLocator, 'Source Element')
|
|
2925
2975
|
|
|
2926
|
-
const dst = await this.
|
|
2927
|
-
|
|
2928
|
-
throw new ElementNotFound(destinationLocator, 'Destination Element')
|
|
2929
|
-
}
|
|
2976
|
+
const dst = await this._locate(destinationLocator)
|
|
2977
|
+
assertElementExists(dst, destinationLocator, 'Destination Element')
|
|
2930
2978
|
|
|
2931
|
-
// Note: Using public api .getClickablePoint
|
|
2932
|
-
const dragSource = await getClickablePoint(src)
|
|
2933
|
-
const dragDestination = await getClickablePoint(dst)
|
|
2979
|
+
// Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
|
|
2980
|
+
const dragSource = await getClickablePoint(src[0])
|
|
2981
|
+
const dragDestination = await getClickablePoint(dst[0])
|
|
2934
2982
|
|
|
2935
2983
|
// Drag start point
|
|
2936
2984
|
await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 })
|
|
@@ -2992,19 +3040,30 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
2992
3040
|
}
|
|
2993
3041
|
return proceedMultiple(els[0])
|
|
2994
3042
|
}
|
|
2995
|
-
|
|
3043
|
+
|
|
3044
|
+
let fieldVal = await el.getProperty('value').then(el => el.jsonValue())
|
|
3045
|
+
|
|
3046
|
+
if (fieldVal === undefined || fieldVal === null) {
|
|
3047
|
+
fieldVal = await el.evaluate(el => el.textContent || el.innerText)
|
|
3048
|
+
}
|
|
3049
|
+
|
|
2996
3050
|
return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal)
|
|
2997
3051
|
}
|
|
2998
3052
|
|
|
2999
3053
|
async function filterFieldsByValue(elements, value, onlySelected) {
|
|
3000
3054
|
const matches = []
|
|
3001
3055
|
for (const element of elements) {
|
|
3002
|
-
|
|
3056
|
+
let val = await element.getProperty('value').then(el => el.jsonValue())
|
|
3057
|
+
|
|
3058
|
+
if (val === undefined || val === null) {
|
|
3059
|
+
val = await element.evaluate(el => el.textContent || el.innerText)
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3003
3062
|
let isSelected = true
|
|
3004
3063
|
if (onlySelected) {
|
|
3005
3064
|
isSelected = await elementSelected(element)
|
|
3006
3065
|
}
|
|
3007
|
-
if ((value == null || val.indexOf(value) > -1) && isSelected) {
|
|
3066
|
+
if ((value == null || (val && val.indexOf(value) > -1)) && isSelected) {
|
|
3008
3067
|
matches.push(element)
|
|
3009
3068
|
}
|
|
3010
3069
|
}
|
|
@@ -3166,7 +3225,12 @@ function _waitForElement(locator, options) {
|
|
|
3166
3225
|
}
|
|
3167
3226
|
}
|
|
3168
3227
|
|
|
3169
|
-
async function findReactElements(locator
|
|
3228
|
+
async function findReactElements(locator) {
|
|
3229
|
+
const resolved = toLocatorConfig(locator, 'react')
|
|
3230
|
+
|
|
3231
|
+
// Use createRequire to access require.resolve in ESM
|
|
3232
|
+
const { createRequire } = await import('module')
|
|
3233
|
+
const require = createRequire(import.meta.url)
|
|
3170
3234
|
const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8')
|
|
3171
3235
|
await this.page.evaluate(resqScript.toString())
|
|
3172
3236
|
|
|
@@ -3209,9 +3273,9 @@ async function findReactElements(locator, props = {}, state = {}) {
|
|
|
3209
3273
|
return [...nodes]
|
|
3210
3274
|
},
|
|
3211
3275
|
{
|
|
3212
|
-
selector:
|
|
3213
|
-
props:
|
|
3214
|
-
state:
|
|
3276
|
+
selector: resolved.react,
|
|
3277
|
+
props: resolved.props || {},
|
|
3278
|
+
state: resolved.state || {},
|
|
3215
3279
|
},
|
|
3216
3280
|
)
|
|
3217
3281
|
|
|
@@ -3227,3 +3291,54 @@ async function findReactElements(locator, props = {}, state = {}) {
|
|
|
3227
3291
|
await arrayHandle.dispose()
|
|
3228
3292
|
return result
|
|
3229
3293
|
}
|
|
3294
|
+
|
|
3295
|
+
async function findByRole(matcher, locator) {
|
|
3296
|
+
const resolved = toLocatorConfig(locator, 'role')
|
|
3297
|
+
const roleSelector = buildRoleSelector(resolved)
|
|
3298
|
+
|
|
3299
|
+
if (!resolved.text && !resolved.name) {
|
|
3300
|
+
return matcher.$$(roleSelector)
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
const allElements = await matcher.$$(roleSelector)
|
|
3304
|
+
const filtered = []
|
|
3305
|
+
const accessibleName = resolved.text ?? resolved.name
|
|
3306
|
+
const matcherFn = createRoleTextMatcher(accessibleName, resolved.exact === true)
|
|
3307
|
+
|
|
3308
|
+
for (const el of allElements) {
|
|
3309
|
+
const texts = await el.evaluate(e => {
|
|
3310
|
+
const ariaLabel = e.hasAttribute('aria-label') ? e.getAttribute('aria-label') : ''
|
|
3311
|
+
const labelText = e.id ? document.querySelector(`label[for="${e.id}"]`)?.textContent.trim() || '' : ''
|
|
3312
|
+
const placeholder = e.getAttribute('placeholder') || ''
|
|
3313
|
+
const innerText = e.innerText ? e.innerText.trim() : ''
|
|
3314
|
+
return [ariaLabel || labelText, placeholder, innerText]
|
|
3315
|
+
})
|
|
3316
|
+
|
|
3317
|
+
if (texts.some(text => matcherFn(text))) filtered.push(el)
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
return filtered
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
function toLocatorConfig(locator, key) {
|
|
3324
|
+
const matchedLocator = new Locator(locator, key)
|
|
3325
|
+
if (matchedLocator.locator) return matchedLocator.locator
|
|
3326
|
+
return { [key]: matchedLocator.value }
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
function buildRoleSelector(resolved) {
|
|
3330
|
+
return `::-p-aria([role="${resolved.role}"])`
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
function createRoleTextMatcher(expected, exactMatch) {
|
|
3334
|
+
if (expected instanceof RegExp) {
|
|
3335
|
+
return value => expected.test(value || '')
|
|
3336
|
+
}
|
|
3337
|
+
const target = String(expected)
|
|
3338
|
+
if (exactMatch) {
|
|
3339
|
+
return value => value === target
|
|
3340
|
+
}
|
|
3341
|
+
return value => typeof value === 'string' && value.includes(target)
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
export { Puppeteer as default }
|