import type * as d from '@stencil/core/internal' ; import { normalizePath } from '@utils' ; import { writeScreenshotData , writeScreenshotImage } from './screenshot-fs' ; import { createHash } from 'crypto' ; import { join , relative } from 'path' ; import { fork } from 'child_process' ; export async function compareScreenshot ( emulateConfig , screenshotBuildData , currentScreenshotBuf , desc , width , height , testPath , pixelmatchThreshold ) { const currentImageHash = createHash ( 'md5' ) . update ( currentScreenshotBuf ) . digest ( 'hex' ) ; const currentImageName = `${ currentImageHash }.png` ; const currentImagePath = join ( screenshotBuildData . imagesDir , currentImageName ) ; await writeScreenshotImage ( currentImagePath , currentScreenshotBuf ) ; currentScreenshotBuf = null ; if ( testPath ) { testPath = normalizePath ( relative ( screenshotBuildData . rootDir , testPath ) ) ; } const screenshotId = getScreenshotId ( emulateConfig , desc ) ; const screenshot = { id : screenshotId , image : currentImageName , device : emulateConfig . device , userAgent : emulateConfig . userAgent , desc : desc , testPath : testPath , width : width , height : height , deviceScaleFactor : emulateConfig . viewport . deviceScaleFactor , hasTouch : emulateConfig . viewport . hasTouch , isLandscape : emulateConfig . viewport . isLandscape , isMobile : emulateConfig . viewport . isMobile , diff : { id : screenshotId , desc : desc , imageA : currentImageName , imageB : currentImageName , mismatchedPixels : 0 , device : emulateConfig . device , userAgent : emulateConfig . userAgent , width : width , height : height , deviceScaleFactor : emulateConfig . viewport . deviceScaleFactor , hasTouch : emulateConfig . viewport . hasTouch , isLandscape : emulateConfig . viewport . isLandscape , isMobile : emulateConfig . viewport . isMobile , allowableMismatchedPixels : screenshotBuildData . allowableMismatchedPixels , allowableMismatchedRatio : screenshotBuildData . allowableMismatchedRatio , testPath : testPath , } , } ; if ( screenshotBuildData . updateMaster ) { await writeScreenshotData ( screenshotBuildData . currentBuildDir , screenshot ) ; return screenshot . diff ; } const masterScreenshotImage = screenshotBuildData . masterScreenshots [ screenshot . id ] ; if ( ! masterScreenshotImage ) { await writeScreenshotData ( screenshotBuildData . currentBuildDir , screenshot ) ; return screenshot . diff ; } screenshot . diff . imageA = masterScreenshotImage ; if ( screenshot . diff . imageA !== screenshot . diff . imageB ) { screenshot . diff . cacheKey = getCacheKey ( screenshot . diff . imageA , screenshot . diff . imageB , pixelmatchThreshold ) ; const cachedMismatchedPixels = screenshotBuildData . cache [ screenshot . diff . cacheKey ] ; if ( typeof cachedMismatchedPixels === 'number' && ! isNaN ( cachedMismatchedPixels ) ) { screenshot . diff . mismatchedPixels = cachedMismatchedPixels ; } else { const naturalWidth = Math . round ( emulateConfig . viewport . width * emulateConfig . viewport . deviceScaleFactor ) ; const naturalHeight = Math . round ( emulateConfig . viewport . height * emulateConfig . viewport . deviceScaleFactor ) ; const pixelMatchInput = { imageAPath : join ( screenshotBuildData . imagesDir , screenshot . diff . imageA ) , imageBPath : join ( screenshotBuildData . imagesDir , screenshot . diff . imageB ) , width : naturalWidth , height : naturalHeight , pixelmatchThreshold : pixelmatchThreshold , } ; screenshot . diff . mismatchedPixels = await getMismatchedPixels ( screenshotBuildData . pixelmatchModulePath , pixelMatchInput ) ; } } await writeScreenshotData ( screenshotBuildData . currentBuildDir , screenshot ) ; return screenshot . diff ; } async function getMismatchedPixels ( pixelmatchModulePath , pixelMatchInput ) { return new Promise < number > ( ( resolve , reject ) => { const timeout = jasmine . DEFAULT_TIMEOUT_INTERVAL * 0.5 ; const tmr = setTimeout ( ( ) => { reject ( `getMismatchedPixels timeout: ${ timeout }ms` ) ; } , timeout ) ; try { const filteredExecArgs = process . execArgv . filter ( ( v ) => ! /^--(debug|inspect)/ . test ( v ) ) ; const options = { execArgv : filteredExecArgs , env : process . env , cwd : process . cwd ( ) , stdio : [ 'pipe' , 'pipe' , 'pipe' , 'ipc' ] as any , } ; const pixelMatchProcess = fork ( pixelmatchModulePath , [ ] , options ) ; pixelMatchProcess . on ( 'message' , ( data ) => { pixelMatchProcess . kill ( ) ; clearTimeout ( tmr ) ; resolve ( data ) ; } ) ; pixelMatchProcess . on ( 'error' , ( err ) => { clearTimeout ( tmr ) ; reject ( err ) ; } ) ; pixelMatchProcess . send ( pixelMatchInput ) ; } catch ( e ) { clearTimeout ( tmr ) ; reject ( `getMismatchedPixels error: ${ e }` ) ; } } ) ; } function getCacheKey ( imageA , imageB , pixelmatchThreshold ) { const hash = createHash ( 'md5' ) ; hash . update ( `${ imageA }:${ imageB }:${ pixelmatchThreshold }` ) ; return hash . digest ( 'hex' ) . substr ( 0 , 10 ) ; } function getScreenshotId ( emulateConfig , uniqueDescription ) { if ( typeof uniqueDescription !== 'string' || uniqueDescription . trim ( ) . length === 0 ) { throw new Error ( `invalid test description` ) ; } const hash = createHash ( 'md5' ) ; hash . update ( uniqueDescription + ':' ) ; hash . update ( emulateConfig . userAgent + ':' ) ; hash . update ( emulateConfig . viewport . width + ':' ) ; hash . update ( emulateConfig . viewport . height + ':' ) ; hash . update ( emulateConfig . viewport . deviceScaleFactor + ':' ) ; hash . update ( emulateConfig . viewport . hasTouch + ':' ) ; hash . update ( emulateConfig . viewport . isMobile + ':' ) ; return hash . digest ( 'hex' ) . substr ( 0 , 8 ) . toLowerCase ( ) ; }