@@ -501,19 +501,19 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
501501 for ( const [ key , value ] of Object . entries ( options [ ignoreOrSelectDOM ] ) ) {
502502 switch ( key ) {
503503 case 'id' :
504- selectors . push ( ...value . map ( e => '#' + e ) ) ;
504+ selectors . push ( ...value . map ( e => e . startsWith ( '#' ) ? e : '#' + e ) ) ;
505505 break ;
506506 case 'class' :
507- selectors . push ( ...value . map ( e => '.' + e ) ) ;
507+ selectors . push ( ...value . map ( e => e . startsWith ( '.' ) ? e : '.' + e ) ) ;
508508 break ;
509509 case 'xpath' :
510- selectors . push ( ...value . map ( e => 'xpath=' + e ) ) ;
510+ selectors . push ( ...value . map ( e => e . startsWith ( 'xpath=' ) ? e : 'xpath=' + e ) ) ;
511511 break ;
512512 case 'cssSelector' :
513513 selectors . push ( ...value ) ;
514514 break ;
515515 case 'coordinates' :
516- selectors . push ( ...value . map ( e => `coordinates=${ e } ` ) ) ;
516+ selectors . push ( ...value . map ( e => `coordinates=${ e } ` ) ) ;
517517 break ;
518518 }
519519 }
@@ -707,52 +707,114 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
707707 optionWarnings . add ( `for snapshot ${ snapshot . name } viewport ${ viewportString } , coordinates may not be accurate for multiple viewports` ) ;
708708 }
709709
710-
710+
711711 const coordinateElement = {
712712 type : 'coordinates' ,
713713 ...validation . coords
714714 } ;
715715 locators . push ( coordinateElement as any ) ;
716716 continue ;
717- }
718-
719- let l = await page . locator ( selector ) . all ( )
720- if ( l . length === 0 ) {
721- optionWarnings . add ( `for snapshot ${ snapshot . name } viewport ${ viewportString } , no element found for selector ${ selector } ` ) ;
722- continue ;
723- }
724- locators . push ( ...l ) ;
725- }
726717
727- for ( const locator of locators ) {
728- if ( locator && typeof locator === 'object' && locator . hasOwnProperty ( 'type' ) && ( locator as any ) . type === 'coordinates' ) {
729- const coordLocator = locator as any ;
730- const { top, bottom, left, right } = coordLocator ;
731- processedOptions [ ignoreOrSelectBoxes ] [ viewportString ] . push ( {
732- left : left ,
733- top : top ,
734- right : right ,
735- bottom : bottom
736- } ) ;
737- continue ;
738- }
739-
740- let bb = await locator . boundingBox ( ) ;
741- if ( bb ) {
742- // Calculate top and bottom from the bounding box properties
743- const top = bb . y ;
744- const bottom = bb . y + bb . height ;
745-
746- // Only push if top and bottom are within the calculated height
747- if ( top <= height && bottom <= height ) {
748- processedOptions [ ignoreOrSelectBoxes ] [ viewportString ] . push ( {
749- left : bb . x ,
750- top : top ,
751- right : bb . x + bb . width ,
752- bottom : bottom
753- } ) ;
718+ } else {
719+ const isXPath = selector . startsWith ( 'xpath=' ) ;
720+ const selectorValue = isXPath ? selector . substring ( 6 ) : selector ;
721+
722+ const boxes = await page . evaluate ( ( { selectorValue, isXPath } ) => {
723+ try {
724+ // First, determine the page height
725+ const DEFAULT_HEIGHT = 16384 ;
726+ const DEFAULT_WIDTH = 7680 ;
727+ const body = document . body ;
728+ const html = document . documentElement ;
729+
730+ let pageHeight ;
731+ let pageWidth ;
732+
733+ if ( ! body || ! html ) {
734+ pageHeight = DEFAULT_HEIGHT ;
735+ pageWidth = DEFAULT_WIDTH ;
736+ } else {
737+ const measurements = [
738+ body ?. scrollHeight || 0 ,
739+ body ?. offsetHeight || 0 ,
740+ html ?. clientHeight || 0 ,
741+ html ?. scrollHeight || 0 ,
742+ html ?. offsetHeight || 0
743+ ] ;
744+
745+ const allMeasurementsInvalid = measurements . every ( measurement => ! measurement ) ;
746+
747+ if ( allMeasurementsInvalid ) {
748+ pageHeight = DEFAULT_HEIGHT ;
749+ } else {
750+ pageHeight = Math . max ( ...measurements ) ;
751+ }
752+
753+ const measurementsWidth = [
754+ body ?. scrollWidth || 0 ,
755+ body ?. offsetWidth || 0 ,
756+ html ?. clientWidth || 0 ,
757+ html ?. scrollWidth || 0 ,
758+ html ?. offsetWidth || 0
759+ ] ;
760+
761+ const allMeasurementsInvalidWidth = measurementsWidth . every ( measurement => ! measurement ) ;
762+
763+ if ( allMeasurementsInvalidWidth ) {
764+ pageWidth = DEFAULT_WIDTH ;
765+ } else {
766+ pageWidth = Math . max ( ...measurementsWidth ) ;
767+ }
768+ }
769+
770+ let elements = [ ] ;
771+
772+ if ( isXPath ) {
773+ // Use XPath evaluation
774+ const xpathResult = document . evaluate (
775+ selectorValue ,
776+ document ,
777+ null ,
778+ XPathResult . ORDERED_NODE_SNAPSHOT_TYPE ,
779+ null
780+ ) ;
781+
782+ for ( let i = 0 ; i < xpathResult . snapshotLength ; i ++ ) {
783+ elements . push ( xpathResult . snapshotItem ( i ) ) ;
784+ }
785+ } else {
786+ elements = Array . from ( document . querySelectorAll ( selectorValue ) ) ;
787+ }
788+
789+ let boxes = [ ] ;
790+
791+ elements . forEach ( ( element ) => {
792+ const boundingBox = element . getBoundingClientRect ( ) ;
793+ if ( boundingBox && boundingBox . width > 0 && boundingBox . height > 0 ) {
794+ const box = {
795+ top : boundingBox . top + window . scrollY ,
796+ left : boundingBox . left + window . scrollX ,
797+ right : boundingBox . left + window . scrollX + boundingBox . width ,
798+ bottom : boundingBox . top + window . scrollY + boundingBox . height
799+ } ;
800+
801+ if ( box . bottom <= pageHeight && box . top >= 0 ) {
802+ boxes . push ( box ) ;
803+ }
804+ }
805+ } ) ;
806+
807+ return boxes ;
808+
809+ } catch ( error ) {
810+ }
811+
812+ } , { selectorValue, isXPath } ) ;
813+
814+ if ( boxes && boxes . length > 1 ) {
815+ processedOptions [ ignoreOrSelectBoxes ] [ viewportString ] . push ( ...boxes ) ;
754816 } else {
755- ctx . log . debug ( `Bounding box for selector skipped due to exceeding height: ${ JSON . stringify ( { top , bottom , height } ) } `) ;
817+ optionWarnings . add ( ` for snapshot ${ snapshot . name } viewport ${ viewportString } , no element found for selector ${ selector } `) ;
756818 }
757819 }
758820 }
0 commit comments