@@ -634,9 +634,11 @@ class Puppeteer extends Helper {
634634 return
635635 }
636636
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
640642
641643 this . withinLocator = new Locator ( locator )
642644 }
@@ -730,11 +732,13 @@ class Puppeteer extends Helper {
730732 * {{ react }}
731733 */
732734 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+ }
735739
736740 // 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 )
738742 await this . page . mouse . move ( x + offsetX , y + offsetY )
739743 return this . _waitForAction ( )
740744 }
@@ -744,9 +748,10 @@ class Puppeteer extends Helper {
744748 *
745749 */
746750 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+ }
750755
751756 await el . click ( )
752757 await el . focus ( )
@@ -758,10 +763,12 @@ class Puppeteer extends Helper {
758763 *
759764 */
760765 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+ }
763770
764- await blurElement ( els [ 0 ] , this . page )
771+ await blurElement ( el , this . page )
765772 return this . _waitForAction ( )
766773 }
767774
@@ -810,11 +817,12 @@ class Puppeteer extends Helper {
810817 }
811818
812819 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+ }
816824 await el . evaluate ( el => el . scrollIntoView ( ) )
817- const elementCoordinates = await getClickablePoint ( els [ 0 ] )
825+ const elementCoordinates = await getClickablePoint ( el )
818826 await this . executeScript ( ( x , y ) => window . scrollBy ( x , y ) , elementCoordinates . x + offsetX , elementCoordinates . y + offsetY )
819827 } else {
820828 await this . executeScript ( ( x , y ) => window . scrollTo ( x , y ) , offsetX , offsetY )
@@ -882,6 +890,21 @@ class Puppeteer extends Helper {
882890 return findElements . call ( this , context , locator )
883891 }
884892
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+
885908 /**
886909 * Find a checkbox by providing human-readable text:
887910 * NOTE: Assumes the checkable element exists
@@ -893,7 +916,9 @@ class Puppeteer extends Helper {
893916 async _locateCheckable ( locator , providedContext = null ) {
894917 const context = providedContext || ( await this . _getContext ( ) )
895918 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+ }
897922 return els [ 0 ]
898923 }
899924
@@ -2124,10 +2149,12 @@ class Puppeteer extends Helper {
21242149 * {{> waitForClickable }}
21252150 */
21262151 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+ }
21292156
2130- return this . waitForFunction ( isElementClickable , [ els [ 0 ] ] , waitTimeout ) . catch ( async e => {
2157+ return this . waitForFunction ( isElementClickable , [ el ] , waitTimeout ) . catch ( async e => {
21312158 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 ) ) {
21322159 throw new Error ( `element ${ new Locator ( locator ) . toString ( ) } still not clickable after ${ waitTimeout || this . options . waitForTimeout / 1000 } sec` )
21332160 } else {
@@ -2701,9 +2728,18 @@ class Puppeteer extends Helper {
27012728
27022729module . exports = Puppeteer
27032730
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+ */
27042738async function findElements ( matcher , locator ) {
27052739 if ( locator . react ) return findReactElements . call ( this , locator )
27062740 locator = new Locator ( locator , 'css' )
2741+
2742+ // Use proven legacy approach - Puppeteer Locator API doesn't have .all() method
27072743 if ( ! locator . isXPath ( ) ) return matcher . $$ ( locator . simplify ( ) )
27082744 // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
27092745 if ( puppeteer . default ?. defaultBrowserRevision ) {
@@ -2712,6 +2748,31 @@ async function findElements(matcher, locator) {
27122748 return matcher . $x ( locator . value )
27132749}
27142750
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+
27152776async function proceedClick ( locator , context = null , options = { } ) {
27162777 let matcher = await this . context
27172778 if ( context ) {
@@ -2857,15 +2918,19 @@ async function findFields(locator) {
28572918}
28582919
28592920async 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+ }
28622925
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+ }
28652930
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 )
28692934
28702935 // Drag start point
28712936 await this . page . mouse . move ( dragSource . x , dragSource . y , { steps : 5 } )
0 commit comments