import { red , yellow } from 'chalk' ; import { access , copySync , outputFile , readFileSync } from 'fs-extra' ; import { join } from 'path' ; import compareImages from '../resemble/compareImages' ; import { calculateDprData , getAndCreatePath , getScreenshotSize } from '../helpers/utils' ; import { DEFAULT_RESIZE_DIMENSIONS } from '../helpers/constants' ; import { determineStatusAddressToolBarRectangles } from './rectangles' ; import { RectanglesOutput } from './rectangles.interfaces' ; import { CompareOptions , CroppedBase64Image , IgnoreBoxes , ImageCompareOptions , ImageCompareResult , ResizeDimensions } from './images.interfaces' ; import { FullPageScreenshotsData } from './screenshots.interfaces' ; import { Executor } from './methods.interface' ; import { CompareData } from '../resemble/compare.interfaces' ; import { LogLevel } from "../helpers/options.interface" ; const { createCanvas , loadImage } = require ( 'canvas' ) ; /**
 * Check if the image exists and create a new baseline image if needed
 */ export async function checkBaselineImageExists ( actualFilePath , baselineFilePath , autoSaveBaseline , logLevel ) { return new Promise ( ( resolve , reject ) => { access ( baselineFilePath , error => { if ( error ) { if ( autoSaveBaseline ) { try { copySync ( actualFilePath , baselineFilePath ) ; if ( logLevel === LogLevel . info ) { console . log ( yellow ( `
#####################################################################################
 INFO:
 Autosaved the image to
 ${ baselineFilePath }
#####################################################################################
` ) ) ; } } catch ( error ) { reject ( red ( `
#####################################################################################
 Image could not be copied. The following error was thrown:
 ${ error }
#####################################################################################
` ) ) ; } } else { reject ( red ( `
#####################################################################################
 Baseline image not found, save the actual image manually to the baseline.
 The image can be found here:
 ${ actualFilePath }
 If you want the module to auto save a non existing image to the baseline you
 can provide 'autoSaveBaseline: true' to the options.
#####################################################################################
` ) ) ; } } resolve ( ) ; } ) ; } ) ; } /**
 * Make a cropped image with Canvas
 */ export async function makeCroppedBase64Image ( { base64Image , rectangles , logLevel , resizeDimensions = DEFAULT_RESIZE_DIMENSIONS , } ) { /**
     * This is in for backwards compatibility, it will be removed in the future
     */ let resizeValues ; if ( typeof resizeDimensions === 'number' ) { resizeValues = { top : resizeDimensions , right : resizeDimensions , bottom : resizeDimensions , left : resizeDimensions , } ; if ( logLevel === LogLevel . debug || logLevel === LogLevel . warn ) { console . log ( yellow ( `
#####################################################################################
 WARNING:
 THE 'resizeDimensions' NEEDS TO BE AN OBJECT LIKE
 {
    top: 10,
    right: 20,
    bottom: 15,
    left: 25,
 }
 NOW IT WILL BE DEFAULTED TO
  {
    top: ${ resizeDimensions },
    right: ${ resizeDimensions },
    bottom: ${ resizeDimensions },
    left: ${ resizeDimensions },
 }
 THIS IS DEPRACATED AND WILL BE REMOVED IN A NEW MAJOR RELEASE
#####################################################################################
` ) ) ; } } else { resizeValues = resizeDimensions ; } const { top , right , bottom , left } = { ... DEFAULT_RESIZE_DIMENSIONS , ... resizeValues } ; const { height , width , x , y } = rectangles ; const canvasWidth = width + left + right ; const canvasHeight = height + top + bottom ; const canvas = createCanvas ( canvasWidth , canvasHeight ) ; const image = await loadImage ( `data:image/png;base64,${ base64Image }` ) ; const ctx = canvas . getContext ( '2d' ) ; let sourceXStart = x - left ; let sourceYStart = y - top ; if ( sourceXStart < 0 ) { if ( logLevel === LogLevel . debug || logLevel === LogLevel . warn ) { console . log ( yellow ( `
#####################################################################################
 THE RESIZE DIMENSION LEFT '${ left }' MADE THE CROPPING GO OUT OF
 THE IMAGE BOUNDARIES RESULTING IN AN IMAGE STARTPOSITION '${ sourceXStart }'.
 THIS HAS BEEN DEFAULTED TO '0'
#####################################################################################
` ) ) ; } sourceXStart = 0 ; } if ( sourceYStart < 0 ) { if ( logLevel === LogLevel . debug || logLevel === LogLevel . warn ) { console . log ( yellow ( `
#####################################################################################
 THE RESIZE DIMENSION LEFT '${ top }' MADE THE CROPPING GO OUT OF
 THE IMAGE BOUNDARIES RESULTING IN AN IMAGE STARTPOSITION '${ sourceYStart }'.
 THIS HAS BEEN DEFAULTED TO '0'
#####################################################################################
` ) ) ; } sourceYStart = 0 ; } ctx . drawImage ( image , sourceXStart , sourceYStart , canvasWidth , canvasHeight , 0 , 0 , canvasWidth , canvasHeight ) ; return canvas . toDataURL ( ) . replace ( /^data:image\/png;base64,/ , '' ) ; } /**
 * Execute the image compare
 */ export async function executeImageCompare ( executor , options , isViewPortScreenshot = false ) { const { devicePixelRatio , fileName , isAndroidNativeWebScreenshot , isHybridApp , logLevel , platformName } = options ; const { actualFolder , autoSaveBaseline , baselineFolder , browserName , deviceName , diffFolder , isMobile , savePerInstance , } = options . folderOptions ; let diffFilePath ; const imageCompareOptions = { ... options . compareOptions . wic , ... options . compareOptions . method } ; const createFolderOptions = { browserName , deviceName , isMobile , savePerInstance } ; const actualFolderPath = getAndCreatePath ( actualFolder , createFolderOptions ) ; const baselineFolderPath = getAndCreatePath ( baselineFolder , createFolderOptions ) ; const actualFilePath = join ( actualFolderPath , fileName ) ; const baselineFilePath = join ( baselineFolderPath , fileName ) ; await checkBaselineImageExists ( actualFilePath , baselineFilePath , autoSaveBaseline , logLevel ) ; const resembleIgnoreDefaults = [ 'alpha' , 'antialiasing' , 'colors' , 'less' , 'nothing' ] ; const ignore = resembleIgnoreDefaults . filter ( option => Object . keys ( imageCompareOptions ) . find ( key => key . toLowerCase ( ) . includes ( option ) && imageCompareOptions [ key ] ) ) ; const blockOut = 'blockOut' in imageCompareOptions ? imageCompareOptions . blockOut : [ ] ; const statusAddressToolBarOptions = { isHybridApp , isMobile , isViewPortScreenshot , platformName , isAndroidNativeWebScreenshot , blockOutStatusBar : imageCompareOptions . blockOutStatusBar , blockOutToolBar : imageCompareOptions . blockOutToolBar , } ; const ignoredBoxes = blockOut . concat ( await determineStatusAddressToolBarRectangles ( executor , statusAddressToolBarOptions ) ) . map ( rectangles => { return calculateDprData ( { bottom : rectangles . y + rectangles . height , right : rectangles . x + rectangles . width , left : rectangles . x , top : rectangles . y , } , devicePixelRatio ) ; } ) ; const compareOptions = { ignore , ... ( ignoredBoxes . length > 0 ? { output : { ignoredBoxes } } : { } ) , scaleToSameSize : imageCompareOptions . scaleImagesToSameSize , } ; const data = await compareImages ( readFileSync ( baselineFilePath ) , readFileSync ( actualFilePath ) , compareOptions ) ; const misMatchPercentage = imageCompareOptions . rawMisMatchPercentage ? data . rawMisMatchPercentage : Number ( data . rawMisMatchPercentage . toFixed ( 2 ) ) ; if ( misMatchPercentage > imageCompareOptions . saveAboveTolerance || logLevel === LogLevel . debug ) { const isDifference = misMatchPercentage > imageCompareOptions . saveAboveTolerance ; const isDifferenceMessage = 'WARNING:\n There was a difference. Saved the difference to' ; const debugMessage = 'INFO:\n Debug mode is enabled. Saved the debug file to:' ; const diffFolderPath = getAndCreatePath ( diffFolder , createFolderOptions ) ; diffFilePath = join ( diffFolderPath , fileName ) ; await saveBase64Image ( await addBlockOuts ( Buffer . from ( data . getBuffer ( ) ) . toString ( 'base64' ) , ignoredBoxes ) , diffFilePath ) ; if ( logLevel === LogLevel . debug || logLevel === LogLevel . warn ) { console . log ( yellow ( `
#####################################################################################
 ${ isDifference ? isDifferenceMessage : debugMessage }
 ${ diffFilePath }
#####################################################################################
` ) ) ; } } return imageCompareOptions . returnAllCompareData ? { fileName , folders : { actual : actualFilePath , baseline : baselineFilePath , ... ( diffFilePath ? { diff : diffFilePath } : { } ) , } , misMatchPercentage , } : misMatchPercentage ; } /**
 * Make a full page image with Canvas
 */ export async function makeFullPageBase64Image ( screenshotsData ) { const amountOfScreenshots = screenshotsData . data . length ; const { fullPageHeight : canvasHeight , fullPageWidth : canvasWidth } = screenshotsData ; const canvas = createCanvas ( canvasWidth , canvasHeight ) ; const ctx = canvas . getContext ( '2d' ) ; for ( let i = 0 ; i < amountOfScreenshots ; i ++ ) { const { canvasYPosition , imageHeight , imageWidth , imageYPosition } = screenshotsData . data [ i ] ; const image = await loadImage ( `data:image/png;base64,${ screenshotsData . data [ i ] . screenshot }` ) ; ctx . drawImage ( image , 0 , imageYPosition , imageWidth , imageHeight , 0 , canvasYPosition , imageWidth , imageHeight ) ; } return canvas . toDataURL ( ) . replace ( /^data:image\/png;base64,/ , '' ) ; } /**
 * Save the base64 image to a file
 */ export async function saveBase64Image ( base64Image , filePath ) { return outputFile ( filePath , base64Image , 'base64' ) ; } /**
 * Create a canvas with the ignore boxes if they are present
 */ export async function addBlockOuts ( screenshot , ignoredBoxes ) { const { height , width } = getScreenshotSize ( screenshot ) ; const canvas = createCanvas ( width , height ) ; const image = await loadImage ( `data:image/png;base64,${ screenshot }` ) ; const canvasContext = canvas . getContext ( '2d' ) ; canvasContext . drawImage ( image , 0 , 0 , width , height , 0 , 0 , width , height ) ; ignoredBoxes . forEach ( ignoredBox => { const { right : ignoredBoxWidth , bottom : ignoredBoxHeight , left : x , top : y } = ignoredBox ; const ignoreCanvas = createCanvas ( ignoredBoxWidth - x , ignoredBoxHeight - y ) ; const ignoreContext = ignoreCanvas . getContext ( '2d' ) ; ignoreContext . globalAlpha = 0.5 ; ignoreContext . fillStyle = '#39aa56' ; ignoreContext . fillRect ( 0 , 0 , ignoredBoxWidth - x , ignoredBoxHeight - y ) ; canvasContext . drawImage ( ignoreCanvas , x , y ) ; } ) ; return canvas . toDataURL ( ) . replace ( /^data:image\/png;base64,/ , '' ) ; }