11import logger from '@wdio/logger'
2- import { join } from 'node:path'
3- import { promises as fsPromises } from 'node:fs'
42import scrollToPosition from '../clientSideScripts/scrollToPosition.js'
53import getDocumentScrollHeight from '../clientSideScripts/getDocumentScrollHeight.js'
64import { calculateDprData , getBase64ScreenshotSize , waitFor } from '../helpers/utils.js'
@@ -15,7 +13,6 @@ import hideRemoveElements from '../clientSideScripts/hideRemoveElements.js'
1513import hideScrollBars from '../clientSideScripts/hideScrollbars.js'
1614import type { ElementRectanglesOptions , RectanglesOutput } from './rectangles.interfaces.js'
1715import { determineElementRectangles } from './rectangles.js'
18- import { cropAndConvertToDataURL , saveBase64Image } from './images.js'
1916
2017const log = logger ( '@wdio/visual-service:@wdio/image-comparison-core:screenshots' )
2118
@@ -280,39 +277,23 @@ export async function getDesktopFullPageScreenshotsData(browserInstance:Webdrive
280277 const { devicePixelRatio, fullPageScrollTimeout, hideAfterFirstScroll, innerHeight } = options
281278 let actualInnerHeight = innerHeight
282279
283- // Detect Safari desktop for debug saving and drop shadow cropping
284280 const { capabilities } = browserInstance
285281 const browserName = ( capabilities ?. browserName || '' ) . toLowerCase ( )
286282 const isSafariDesktop = browserName . includes ( 'safari' ) && ! browserInstance . isMobile
287- const debugDir = isSafariDesktop ? join ( process . cwd ( ) , '.tmp' , 'debug' , 'safari-desktop-fullpage-screenshots' ) : null
288283
289- // Safari desktop has a drop shadow at the top
290- // The drop shadow is always 1 device pixel, which translates to 1 * DPR CSS pixels
291284 const safariTopDropShadowCssPixels = isSafariDesktop ? Math . round ( 1 * devicePixelRatio ) : 0
292- // For Safari desktop, calculate effective scroll increment
293- // First image: scroll by 0, use full height (716px)
294- // Subsequent images: scroll by (actualInnerHeight - dropShadowOffset) = 715px, crop 1px from top
285+ const safariBottomCropOffsetCssPixels = isSafariDesktop ? Math . round ( 10 * devicePixelRatio ) : 0
286+
295287 const effectiveScrollIncrement = isSafariDesktop
296- ? actualInnerHeight - safariTopDropShadowCssPixels
288+ ? actualInnerHeight - safariTopDropShadowCssPixels - safariBottomCropOffsetCssPixels
297289 : actualInnerHeight
298290
299- // Create debug directory if needed
300- if ( debugDir ) {
301- await fsPromises . mkdir ( debugDir , { recursive : true } )
302- }
303-
304291 // Start with an empty array, during the scroll it will be filled because a page could also have a lazy loading
305292 const amountOfScrollsArray = [ ]
306293 let scrollHeight : number | undefined
307294 let screenshotSize
308295
309296 for ( let i = 0 ; i <= amountOfScrollsArray . length ; i ++ ) {
310- // Determine and start scrolling
311- // For Safari desktop: first image scrolls to 0, subsequent images scroll by effectiveScrollIncrement (715px)
312- // Image 0: scrollY = 0
313- // Image 1: scrollY = 715 (effectiveScrollIncrement)
314- // Image 2: scrollY = 1430 (2 * effectiveScrollIncrement)
315- // etc.
316297 const scrollY = isSafariDesktop
317298 ? ( i === 0 ? 0 : i * effectiveScrollIncrement )
318299 : actualInnerHeight * i
@@ -344,161 +325,75 @@ export async function getDesktopFullPageScreenshotsData(browserInstance:Webdrive
344325 // and SafariDriver for Safari 11
345326 }
346327
347- // Determine scroll height and check if we need to scroll again
348328 scrollHeight = await browserInstance . execute ( getDocumentScrollHeight )
349329
350- // For Safari desktop, use effectiveScrollIncrement for the scroll check
351330 const scrollCheckHeight = isSafariDesktop ? effectiveScrollIncrement : actualInnerHeight
352331 if ( scrollHeight && ( scrollY + scrollCheckHeight < scrollHeight ) && screenshotSize . height === actualInnerHeight ) {
353332 amountOfScrollsArray . push ( amountOfScrollsArray . length )
354333 }
355334 // There is no else, Lazy load and large screenshots,
356335 // like with older drivers such as FF <= 47 and IE11, will not work
357336
358- // The height of the image of the last 1 could be different
359- // For Safari desktop, account for first image being full height and subsequent images being cropped
360337 const isFirstImage = i === 0
361338 const isLastImage = amountOfScrollsArray . length === i
362339 let imageHeight : number
363340 if ( scrollHeight && isLastImage ) {
364341 if ( isSafariDesktop ) {
365- // Calculate remaining content: scrollHeight - (firstImageHeight + (numberOfPreviousImages - 1) * effectiveScrollIncrement)
366342 const numberOfPreviousImages = viewportScreenshots . length
367343 const totalPreviousHeight = numberOfPreviousImages === 0
368344 ? 0
369345 : actualInnerHeight + ( numberOfPreviousImages - 1 ) * effectiveScrollIncrement
370346 const remainingContent = scrollHeight - totalPreviousHeight
371347
372- // For the last image, we need to be smart:
373- // - If remainingContent >= actualInnerHeight: it's a full screenshot, treat it like a regular non-first image
374- // (crop 1px from top, visible height = 715px)
375- // - If remainingContent < actualInnerHeight: it's a partial screenshot
376- // For partial screenshots, we're cropping from a position that doesn't include the drop shadow at pixel 0
377- // So we don't need to add 1px - just use remainingContent directly
378348 imageHeight = remainingContent >= actualInnerHeight
379- ? effectiveScrollIncrement // Full screenshot: treat like regular non-first image
380- : remainingContent // Partial screenshot: use remainingContent directly (no drop shadow in cropped region)
349+ ? effectiveScrollIncrement + safariBottomCropOffsetCssPixels
350+ : remainingContent + safariBottomCropOffsetCssPixels
381351 } else {
382352 imageHeight = scrollHeight - actualInnerHeight * viewportScreenshots . length
383353 }
384354 } else {
385- // Non-last images: use full height for first, effectiveScrollIncrement for subsequent
386355 imageHeight = isSafariDesktop && ! isFirstImage
387356 ? effectiveScrollIncrement
388357 : screenshotSize . height
389358 }
390359
391- // The starting position for cropping could be different for the last image (0 means no cropping)
392- // For Safari desktop, crop 1px from top for all images except first
360+ if ( isSafariDesktop && isFirstImage && safariBottomCropOffsetCssPixels > 0 ) {
361+ imageHeight -= safariBottomCropOffsetCssPixels
362+ }
363+
393364 let imageYPosition : number
394365 if ( isSafariDesktop ) {
395366 if ( isLastImage && ! isFirstImage ) {
396- // Last image: need to handle two cases
397367 const numberOfPreviousImages = viewportScreenshots . length
398368 const totalPreviousHeight = numberOfPreviousImages === 0
399369 ? 0
400370 : actualInnerHeight + ( numberOfPreviousImages - 1 ) * effectiveScrollIncrement
401371 const remainingContent = scrollHeight ? scrollHeight - totalPreviousHeight : 0
402372
403- // Full screenshot: treat like regular non-first image (crop 1px from top)
404- // Partial screenshot: we want to show the last remainingContent pixels
405- // imageHeight = remainingContent, so we start at: 716 - remainingContent
406- // For partial screenshots, the drop shadow is at pixel 0, but we're cropping from a different position
407- // so we don't need to crop the drop shadow - just position to get the last remainingContent pixels
408373 imageYPosition = remainingContent >= actualInnerHeight
409374 ? safariTopDropShadowCssPixels
410- : actualInnerHeight - remainingContent
375+ : actualInnerHeight - remainingContent - safariBottomCropOffsetCssPixels
411376 } else if ( ! isFirstImage ) {
412- // Non-last, non-first images: crop 1px from top
413377 imageYPosition = safariTopDropShadowCssPixels
414378 } else {
415- // First image: no crop
416379 imageYPosition = 0
417380 }
418381 } else {
419- // Non-Safari: standard calculation
420382 imageYPosition = isLastImage && ! isFirstImage
421383 ? actualInnerHeight - imageHeight
422384 : 0
423385 }
424386
425- // Debug logging for Safari desktop to see what's being cropped
426- if ( isSafariDesktop ) {
427- // For non-first images, imageHeight already accounts for the crop, so visible = imageHeight
428- // For first image, visible = imageHeight (no crop)
429- const visibleHeightAfterCrop = imageHeight
430- console . log ( `Safari desktop screenshot ${ i } cropping details:` , {
431- isFirstImage,
432- isLastImage,
433- scrollY,
434- screenshotSize : {
435- width : screenshotSize . width ,
436- height : screenshotSize . height ,
437- } ,
438- imageHeight,
439- imageYPosition,
440- visibleHeightAfterCrop,
441- actualInnerHeight,
442- effectiveScrollIncrement,
443- remainingContent : isLastImage && scrollHeight
444- ? ( ( ) => {
445- const numberOfPreviousImages = viewportScreenshots . length
446- const totalPreviousHeight = numberOfPreviousImages === 0
447- ? 0
448- : actualInnerHeight + ( numberOfPreviousImages - 1 ) * effectiveScrollIncrement
449- return scrollHeight - totalPreviousHeight
450- } ) ( )
451- : undefined ,
452- cropFromOriginal : {
453- startY : imageYPosition ,
454- height : imageHeight ,
455- endY : imageYPosition + imageHeight ,
456- } ,
457- } )
458- }
459-
460- // Calculate canvasYPosition for stitching
461- // For Safari desktop: first image at 0, subsequent images start where previous image ends
462387 let canvasYPosition : number
463388 if ( isSafariDesktop && ! isFirstImage ) {
464- // Calculate based on where the previous image ends
465- // Previous image's canvasYPosition + previous image's height
466389 const previousImage = viewportScreenshots [ viewportScreenshots . length - 1 ]
467390 canvasYPosition = previousImage
468391 ? previousImage . canvasYPosition + previousImage . imageHeight
469- : actualInnerHeight + ( i - 1 ) * effectiveScrollIncrement // Fallback (shouldn't happen)
392+ : actualInnerHeight + ( i - 1 ) * effectiveScrollIncrement
470393 } else {
471394 canvasYPosition = isSafariDesktop ? 0 : scrollY
472395 }
473396
474- // Calculate crop dimensions in device pixels for debug saving
475- const imageXPositionDevicePixels = 0
476- const imageYPositionDevicePixels = Math . round ( imageYPosition * devicePixelRatio )
477- const imageWidthDevicePixels = Math . round ( screenshotSize . width * devicePixelRatio )
478- const imageHeightDevicePixels = Math . round ( imageHeight * devicePixelRatio )
479-
480- // Save cropped screenshot to debug folder for Safari desktop
481- if ( isSafariDesktop && debugDir ) {
482- try {
483- const croppedBase64 = await cropAndConvertToDataURL ( {
484- addIOSBezelCorners : false ,
485- base64Image : screenshot ,
486- deviceName : '' ,
487- devicePixelRatio : 1 , // Already in device pixels
488- height : imageHeightDevicePixels ,
489- isIOS : false ,
490- isLandscape : false ,
491- sourceX : imageXPositionDevicePixels ,
492- sourceY : imageYPositionDevicePixels ,
493- width : imageWidthDevicePixels ,
494- } )
495- const debugFilePath = join ( debugDir , `step-${ i } -scrollY-${ scrollY } -height-${ imageHeight } -yPos-${ imageYPosition } .png` )
496- await saveBase64Image ( croppedBase64 , debugFilePath )
497- } catch ( error ) {
498- log . warn ( `Failed to save debug screenshot for step ${ i } :` , error )
499- }
500- }
501-
502397 // Store all the screenshot data in the screenshot object
503398 viewportScreenshots . push ( {
504399 ...calculateDprData (
0 commit comments