@@ -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