@@ -634,9 +634,11 @@ class Puppeteer extends Helper {
634
634
return
635
635
}
636
636
637
- const els = await this . _locate ( locator )
638
- assertElementExists ( els , locator )
639
- this . context = els [ 0 ]
637
+ const el = await this . _locateElement ( locator )
638
+ if ( ! el ) {
639
+ throw new ElementNotFound ( locator , 'Element for within context' )
640
+ }
641
+ this . context = el
640
642
641
643
this . withinLocator = new Locator ( locator )
642
644
}
@@ -730,11 +732,13 @@ class Puppeteer extends Helper {
730
732
* {{ react }}
731
733
*/
732
734
async moveCursorTo ( locator , offsetX = 0 , offsetY = 0 ) {
733
- const els = await this . _locate ( locator )
734
- assertElementExists ( els , locator )
735
+ const el = await this . _locateElement ( locator )
736
+ if ( ! el ) {
737
+ throw new ElementNotFound ( locator , 'Element to move cursor to' )
738
+ }
735
739
736
740
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
737
- const { x, y } = await getClickablePoint ( els [ 0 ] )
741
+ const { x, y } = await getClickablePoint ( el )
738
742
await this . page . mouse . move ( x + offsetX , y + offsetY )
739
743
return this . _waitForAction ( )
740
744
}
@@ -744,9 +748,10 @@ class Puppeteer extends Helper {
744
748
*
745
749
*/
746
750
async focus ( locator ) {
747
- const els = await this . _locate ( locator )
748
- assertElementExists ( els , locator , 'Element to focus' )
749
- const el = els [ 0 ]
751
+ const el = await this . _locateElement ( locator )
752
+ if ( ! el ) {
753
+ throw new ElementNotFound ( locator , 'Element to focus' )
754
+ }
750
755
751
756
await el . click ( )
752
757
await el . focus ( )
@@ -758,10 +763,12 @@ class Puppeteer extends Helper {
758
763
*
759
764
*/
760
765
async blur ( locator ) {
761
- const els = await this . _locate ( locator )
762
- assertElementExists ( els , locator , 'Element to blur' )
766
+ const el = await this . _locateElement ( locator )
767
+ if ( ! el ) {
768
+ throw new ElementNotFound ( locator , 'Element to blur' )
769
+ }
763
770
764
- await blurElement ( els [ 0 ] , this . page )
771
+ await blurElement ( el , this . page )
765
772
return this . _waitForAction ( )
766
773
}
767
774
@@ -810,11 +817,12 @@ class Puppeteer extends Helper {
810
817
}
811
818
812
819
if ( locator ) {
813
- const els = await this . _locate ( locator )
814
- assertElementExists ( els , locator , 'Element' )
815
- const el = els [ 0 ]
820
+ const el = await this . _locateElement ( locator )
821
+ if ( ! el ) {
822
+ throw new ElementNotFound ( locator , 'Element to scroll into view' )
823
+ }
816
824
await el . evaluate ( el => el . scrollIntoView ( ) )
817
- const elementCoordinates = await getClickablePoint ( els [ 0 ] )
825
+ const elementCoordinates = await getClickablePoint ( el )
818
826
await this . executeScript ( ( x , y ) => window . scrollBy ( x , y ) , elementCoordinates . x + offsetX , elementCoordinates . y + offsetY )
819
827
} else {
820
828
await this . executeScript ( ( x , y ) => window . scrollTo ( x , y ) , offsetX , offsetY )
@@ -882,6 +890,21 @@ class Puppeteer extends Helper {
882
890
return findElements . call ( this , context , locator )
883
891
}
884
892
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
+
885
908
/**
886
909
* Find a checkbox by providing human-readable text:
887
910
* NOTE: Assumes the checkable element exists
@@ -893,7 +916,9 @@ class Puppeteer extends Helper {
893
916
async _locateCheckable ( locator , providedContext = null ) {
894
917
const context = providedContext || ( await this . _getContext ( ) )
895
918
const els = await findCheckable . call ( this , locator , context )
896
- assertElementExists ( els [ 0 ] , locator , 'Checkbox or radio' )
919
+ if ( ! els || els . length === 0 ) {
920
+ throw new ElementNotFound ( locator , 'Checkbox or radio' )
921
+ }
897
922
return els [ 0 ]
898
923
}
899
924
@@ -2124,10 +2149,12 @@ class Puppeteer extends Helper {
2124
2149
* {{> waitForClickable }}
2125
2150
*/
2126
2151
async waitForClickable ( locator , waitTimeout ) {
2127
- const els = await this . _locate ( locator )
2128
- assertElementExists ( els , locator )
2152
+ const el = await this . _locateElement ( locator )
2153
+ if ( ! el ) {
2154
+ throw new ElementNotFound ( locator , 'Element to wait for clickable' )
2155
+ }
2129
2156
2130
- return this . waitForFunction ( isElementClickable , [ els [ 0 ] ] , waitTimeout ) . catch ( async e => {
2157
+ return this . waitForFunction ( isElementClickable , [ el ] , waitTimeout ) . catch ( async e => {
2131
2158
if ( / W a i t i n g f a i l e d / i. test ( e . message ) || / f a i l e d : t i m e o u t / i. test ( e . message ) ) {
2132
2159
throw new Error ( `element ${ new Locator ( locator ) . toString ( ) } still not clickable after ${ waitTimeout || this . options . waitForTimeout / 1000 } sec` )
2133
2160
} else {
@@ -2701,9 +2728,18 @@ class Puppeteer extends Helper {
2701
2728
2702
2729
module . exports = Puppeteer
2703
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
+ */
2704
2738
async function findElements ( matcher , locator ) {
2705
2739
if ( locator . react ) return findReactElements . call ( this , locator )
2706
2740
locator = new Locator ( locator , 'css' )
2741
+
2742
+ // Use proven legacy approach - Puppeteer Locator API doesn't have .all() method
2707
2743
if ( ! locator . isXPath ( ) ) return matcher . $$ ( locator . simplify ( ) )
2708
2744
// puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2709
2745
if ( puppeteer . default ?. defaultBrowserRevision ) {
@@ -2712,6 +2748,31 @@ async function findElements(matcher, locator) {
2712
2748
return matcher . $x ( locator . value )
2713
2749
}
2714
2750
2751
+ /**
2752
+ * Find a single element using Puppeteer's native element discovery methods
2753
+ * Note: Puppeteer Locator API doesn't have .first() method like Playwright
2754
+ * @param {Object } matcher - Puppeteer context to search within
2755
+ * @param {Object|string } locator - Locator specification
2756
+ * @returns {Promise<Object> } Single ElementHandle object
2757
+ */
2758
+ async function findElement ( matcher , locator ) {
2759
+ if ( locator . react ) return findReactElements . call ( this , locator )
2760
+ locator = new Locator ( locator , 'css' )
2761
+
2762
+ // Use proven legacy approach - Puppeteer Locator API doesn't have .first() method
2763
+ if ( ! locator . isXPath ( ) ) {
2764
+ const elements = await matcher . $$ ( locator . simplify ( ) )
2765
+ return elements [ 0 ]
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 ]
2774
+ }
2775
+
2715
2776
async function proceedClick ( locator , context = null , options = { } ) {
2716
2777
let matcher = await this . context
2717
2778
if ( context ) {
@@ -2857,15 +2918,19 @@ async function findFields(locator) {
2857
2918
}
2858
2919
2859
2920
async function proceedDragAndDrop ( sourceLocator , destinationLocator ) {
2860
- const src = await this . _locate ( sourceLocator )
2861
- assertElementExists ( src , sourceLocator , 'Source Element' )
2921
+ const src = await this . _locateElement ( sourceLocator )
2922
+ if ( ! src ) {
2923
+ throw new ElementNotFound ( sourceLocator , 'Source Element' )
2924
+ }
2862
2925
2863
- const dst = await this . _locate ( destinationLocator )
2864
- assertElementExists ( dst , destinationLocator , 'Destination Element' )
2926
+ const dst = await this . _locateElement ( destinationLocator )
2927
+ if ( ! dst ) {
2928
+ throw new ElementNotFound ( destinationLocator , 'Destination Element' )
2929
+ }
2865
2930
2866
- // Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
2867
- const dragSource = await getClickablePoint ( src [ 0 ] )
2868
- const dragDestination = await getClickablePoint ( dst [ 0 ] )
2931
+ // Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
2932
+ const dragSource = await getClickablePoint ( src )
2933
+ const dragDestination = await getClickablePoint ( dst )
2869
2934
2870
2935
// Drag start point
2871
2936
await this . page . mouse . move ( dragSource . x , dragSource . y , { steps : 5 } )
0 commit comments