@@ -315,14 +315,34 @@ export async function getDesktopFullPageScreenshotsData(browserInstance:Webdrive
315315 const { devicePixelRatio, fullPageScrollTimeout, hideAfterFirstScroll, innerHeight } = options
316316 let actualInnerHeight = innerHeight
317317
318+ const { capabilities } = browserInstance
319+ const browserName = ( capabilities ?. browserName || '' ) . toLowerCase ( )
320+ // Safari desktop returns the browser mask with rounded corners and a drop shadow, so we need to fix this
321+ const isSafariDesktop = browserName . includes ( 'safari' ) && ! browserInstance . isMobile
322+ const safariTopDropShadowCssPixels = isSafariDesktop ? Math . round ( 1 * devicePixelRatio ) : 0
323+ const safariBottomCropOffsetCssPixels = isSafariDesktop ? Math . round ( 10 * devicePixelRatio ) : 0
324+ // For Safari desktop, calculate effective scroll increment
325+ // First image: scroll by 0, use full height (e.g.716px), crop 10px from bottom
326+ // Subsequent images: scroll by (actualInnerHeight - dropShadowOffset - bottomCropOffset) = 705px, crop 1px from top and 10px from bottom
327+ const effectiveScrollIncrement = isSafariDesktop
328+ ? actualInnerHeight - safariTopDropShadowCssPixels - safariBottomCropOffsetCssPixels
329+ : actualInnerHeight
318330 // Start with an empty array, during the scroll it will be filled because a page could also have a lazy loading
319331 const amountOfScrollsArray = [ ]
320332 let scrollHeight : number | undefined
321333 let screenshotSize
322334
323335 for ( let i = 0 ; i <= amountOfScrollsArray . length ; i ++ ) {
324336 // Determine and start scrolling
325- const scrollY = actualInnerHeight * i
337+ // For Safari desktop: first image scrolls to 0, subsequent images scroll by effectiveScrollIncrement (715px)
338+ // Image 0: scrollY = 0
339+ // Image 1: scrollY = 715 (effectiveScrollIncrement)
340+ // Image 2: scrollY = 1430 (2 * effectiveScrollIncrement)
341+ // etc.
342+ const scrollY = isSafariDesktop
343+ ? ( i === 0 ? 0 : i * effectiveScrollIncrement )
344+ : actualInnerHeight * i
345+
326346 await browserInstance . execute ( scrollToPosition , scrollY )
327347
328348 // Simply wait the amount of time specified for lazy-loading
@@ -351,30 +371,122 @@ export async function getDesktopFullPageScreenshotsData(browserInstance:Webdrive
351371 // and SafariDriver for Safari 11
352372 }
353373
354- // Determine scroll height and check if we need to scroll again
355374 scrollHeight = await browserInstance . execute ( getDocumentScrollHeight )
356375
357- if ( scrollHeight && ( scrollY + actualInnerHeight < scrollHeight ) && screenshotSize . height === actualInnerHeight ) {
376+ // For Safari desktop, use effectiveScrollIncrement for the scroll check
377+ const scrollCheckHeight = isSafariDesktop ? effectiveScrollIncrement : actualInnerHeight
378+
379+ if ( scrollHeight && ( scrollY + scrollCheckHeight < scrollHeight ) && screenshotSize . height === actualInnerHeight ) {
358380 amountOfScrollsArray . push ( amountOfScrollsArray . length )
359381 }
360- // There is no else, Lazy load and large screenshots,
361- // like with older drivers such as FF <= 47 and IE11, will not work
362382
363383 // The height of the image of the last 1 could be different
364- const imageHeight : number = scrollHeight && amountOfScrollsArray . length === i
365- ? scrollHeight - actualInnerHeight * viewportScreenshots . length
366- : screenshotSize . height
384+ // For Safari desktop, account for first image being full height and subsequent images being cropped
385+ const isFirstImage = i === 0
386+ const isLastImage = amountOfScrollsArray . length === i
387+ let imageHeight : number
388+ if ( scrollHeight && isLastImage ) {
389+ if ( isSafariDesktop ) {
390+ // Calculate remaining content: scrollHeight - (firstImageHeight + (numberOfPreviousImages - 1) * effectiveScrollIncrement)
391+ const numberOfPreviousImages = viewportScreenshots . length
392+ const totalPreviousHeight = numberOfPreviousImages === 0
393+ ? 0
394+ : actualInnerHeight + ( numberOfPreviousImages - 1 ) * effectiveScrollIncrement
395+ const remainingContent = scrollHeight - totalPreviousHeight
396+ // For the last image, we need to be smart:
397+ // - If remainingContent >= actualInnerHeight: it's a full screenshot, treat it like a regular non-first image
398+ // (crop 1px from top, visible height = 705px, but last image doesn't crop bottom, so add 10px)
399+ // - If remainingContent < actualInnerHeight: it's a partial screenshot
400+ // For partial screenshots, we're cropping from a position that doesn't include the drop shadow at pixel 0
401+ // Last image doesn't crop bottom, so we need to add 10px to account for that
402+ imageHeight = remainingContent >= actualInnerHeight
403+ ? effectiveScrollIncrement + safariBottomCropOffsetCssPixels
404+ : remainingContent + safariBottomCropOffsetCssPixels
405+ } else {
406+ imageHeight = scrollHeight - actualInnerHeight * viewportScreenshots . length
407+ }
408+ } else {
409+ // Non-last images: use full height for first, effectiveScrollIncrement for subsequent
410+ // For non-first images, effectiveScrollIncrement already accounts for top and bottom crops
411+ imageHeight = isSafariDesktop && ! isFirstImage
412+ ? effectiveScrollIncrement
413+ : screenshotSize . height
414+ }
415+
367416 // The starting position for cropping could be different for the last image (0 means no cropping)
368- const imageYPosition = amountOfScrollsArray . length === i && amountOfScrollsArray . length !== 0
369- ? actualInnerHeight - imageHeight
370- : 0
417+ // For Safari desktop, crop 1px from top for all images except first
418+ if ( isSafariDesktop && isFirstImage && safariBottomCropOffsetCssPixels > 0 ) {
419+ imageHeight -= safariBottomCropOffsetCssPixels
420+ }
421+
422+ // The starting position for cropping could be different for the last image (0 means no cropping)
423+ // For Safari desktop, crop 1px from top for all images except first
424+ let imageYPosition : number
425+ if ( isSafariDesktop ) {
426+ if ( isLastImage && ! isFirstImage ) {
427+ // Last image: need to handle two cases
428+ const numberOfPreviousImages = viewportScreenshots . length
429+ const totalPreviousHeight = numberOfPreviousImages === 0
430+ ? 0
431+ : actualInnerHeight + ( numberOfPreviousImages - 1 ) * effectiveScrollIncrement
432+ const remainingContent = scrollHeight ? scrollHeight - totalPreviousHeight : 0
433+
434+ // Full screenshot: treat like regular non-first image (crop 1px from top)
435+ // Partial screenshot: we want to show the last remainingContent pixels
436+ // But we need to include the bottom 10px that we're not cropping, so start 10px higher
437+ // imageHeight = remainingContent, so we start at: 716 - remainingContent - 10px
438+ // This way we crop 10px higher to include the bottom corners
439+ imageYPosition = remainingContent >= actualInnerHeight
440+ ? safariTopDropShadowCssPixels
441+ : actualInnerHeight - remainingContent - safariBottomCropOffsetCssPixels
442+
443+ // If remainingContent is too small, we might get negative imageYPosition or invalid dimensions
444+ if ( imageYPosition < 0 ) {
445+ imageYPosition = actualInnerHeight - remainingContent
446+ imageHeight = remainingContent
447+ } else if ( imageYPosition + imageHeight > screenshotSize . height ) {
448+ imageHeight = screenshotSize . height - imageYPosition
449+ }
450+ } else if ( ! isFirstImage ) {
451+ // Non-last, non-first images: crop 1px from top
452+ imageYPosition = safariTopDropShadowCssPixels
453+ } else {
454+ // First image: no crop
455+ imageYPosition = 0
456+ }
457+ } else {
458+ imageYPosition = isLastImage && ! isFirstImage
459+ ? actualInnerHeight - imageHeight
460+ : 0
461+ }
462+
463+ // Ensure imageYPosition and imageHeight are valid for all cases
464+ if ( imageYPosition < 0 ) {
465+ imageHeight += imageYPosition
466+ imageYPosition = 0
467+ }
468+ if ( imageYPosition + imageHeight > screenshotSize . height ) {
469+ imageHeight = screenshotSize . height - imageYPosition
470+ }
471+
472+ // Calculate based on where the previous image ends
473+ // Previous image's canvasYPosition + previous image's height
474+ let canvasYPosition : number
475+ if ( isSafariDesktop && ! isFirstImage ) {
476+ const previousImage = viewportScreenshots [ viewportScreenshots . length - 1 ]
477+ canvasYPosition = previousImage
478+ ? previousImage . canvasYPosition + previousImage . imageHeight
479+ : actualInnerHeight + ( i - 1 ) * effectiveScrollIncrement
480+ } else {
481+ canvasYPosition = isSafariDesktop ? 0 : scrollY
482+ }
371483
372484 // Store all the screenshot data in the screenshot object
373485 viewportScreenshots . push ( {
374486 ...calculateDprData (
375487 {
376488 canvasWidth : screenshotSize . width ,
377- canvasYPosition : scrollY ,
489+ canvasYPosition : canvasYPosition ,
378490 imageHeight : imageHeight ,
379491 imageWidth : screenshotSize . width ,
380492 imageXPosition : 0 ,
0 commit comments