import { rgb , nest } from 'd3' ; import { FtrProviderContext } from '../ftr_provider_context' ; interface ColorStat { color : string ; percentage : number ; pixels ? : number ; withinTolerance ? : boolean ; } export type CanvasElementColorStats = ColorStat [ ] ; function getRoundedChannel ( value , tolerance ) { return Math . round ( value / tolerance ) * tolerance ; } export async function CanvasElementProvider ( { getService } ) { const { driver } = await getService ( '__webdriver__' ) . init ( ) ; return new ( class CanvasElementService { public async disableAntiAliasing ( ) { await driver . executeScript ( `
        document.body.style["font-smooth"] = "never";
        document.body.style["-webkit-font-smoothing"] = "none";
        document.body.classList.add("mlDisableAntiAliasing");
        ` ) ; } public async resetAntiAliasing ( ) { await driver . executeScript ( `
        document.body.style["font-smooth"] = "";
        document.body.style["-webkit-font-smoothing"] = "";
        document.body.classList.remove("mlDisableAntiAliasing");
        ` ) ; } param selector returns { Promise < number [ ] > } public async getImageData ( selector ) { return await driver . executeScript ( `
        try {
          const el = document.querySelector('${ selector }');
          const ctx = el.getContext('2d');
          return ctx.getImageData(0, 0, el.width, el.height).data;
        } catch(e) {
          return [];
        }
        ` ) ; } param selector param expectedColorStats param percentageThreshold param channelTolerance param exclude returns public async getColorStats ( selector , expectedColorStats ? , exclude ? , percentageThreshold = 5 , channelTolerance = 10 , valueTolerance = 10 ) { const imageData = await this . getImageData ( selector ) ; const colors = [ ] ; for ( let i = 0 ; i < imageData . length ; i += 4 ) { const r = getRoundedChannel ( imageData [ i ] , channelTolerance ) ; const g = getRoundedChannel ( imageData [ i + 1 ] , channelTolerance ) ; const b = getRoundedChannel ( imageData [ i + 2 ] , channelTolerance ) ; const color = rgb ( r , g , b ) . toString ( ) . toUpperCase ( ) ; if ( exclude === undefined || ! exclude . includes ( color ) ) colors . push ( color ) ; } function getPixelPercentage ( pixelsNum ) { return ( pixelsNum / colors . length ) * 100 ; } return nest < string > ( ) . key ( ( d ) => d ) . entries ( colors ) . filter ( ( s ) => getPixelPercentage ( s . values . length ) >= percentageThreshold ) . sort ( ( a , b ) => a . key . localeCompare ( b . key ) ) . map ( ( s , i ) => { const percentage = getPixelPercentage ( s . values . length ) ; const pixels = s . values . length ; return { color : s . key , percentage , pixels , ... ( expectedColorStats !== undefined ? { withinTolerance : this . isValueWithinTolerance ( percentage , pixels , expectedColorStats [ i ] ?. percentage , valueTolerance ) && this . isColorWithinTolerance ( s . key , expectedColorStats [ i ] ?. color , channelTolerance ) , } : { } ) , } ; } ) ; } /**
         * Same as getColorStats() but also checks if each supplied
         * expected color lies within channelTolerance.
         */ public async getColorStatsWithColorTolerance ( selector , expectedColorStats , exclude ? , percentageThreshold = 0 , channelTolerance = 10 , valueTolerance = 10 ) { const actualColorStats = await this . getColorStats ( selector , undefined , exclude , percentageThreshold , channelTolerance , valueTolerance ) ; return expectedColorStats . map ( ( expectedColor ) => { const colorsWithinTolerance = actualColorStats . filter ( ( d ) => this . isColorWithinTolerance ( d . color , expectedColor . color , channelTolerance ) ) ; const colorPercentageWithinTolerance = colorsWithinTolerance . reduce ( ( sum , x ) => sum + x . percentage , 0 ) ; const pixelsWithinTolerance = colorsWithinTolerance . reduce ( ( sum , x ) => sum + ( x . pixels || 0 ) , 0 ) ; return { color : expectedColor . color , percentage : colorPercentageWithinTolerance , pixels : pixelsWithinTolerance , withinTolerance : this . isValueWithinTolerance ( colorPercentageWithinTolerance , pixelsWithinTolerance , expectedColor . percentage , valueTolerance ) , } ; } ) ; } param actualColor param expectedColor param toleranceRange returns public isColorWithinTolerance ( actualColor , expectedColor , toleranceRange = 10 ) { const actualRGB = rgb ( actualColor ) ; const expectedRGB = rgb ( expectedColor ) ; return ( getRoundedChannel ( expectedRGB . r , toleranceRange ) === getRoundedChannel ( actualRGB . r , toleranceRange ) && getRoundedChannel ( expectedRGB . g , toleranceRange ) === getRoundedChannel ( actualRGB . g , toleranceRange ) && getRoundedChannel ( expectedRGB . b , toleranceRange ) === getRoundedChannel ( actualRGB . b , toleranceRange ) ) ; } param actualPercentage param actualPixels param expectedPercentage param toleranceRange returns public isValueWithinTolerance ( actualPercentage , actualPixels , expectedPercentage , toleranceRange = 10 ) { const lower = expectedPercentage - toleranceRange / 2 ; const upper = expectedPercentage + toleranceRange / 2 ; return ( actualPixels > 0 && lower <= actualPercentage && upper >= actualPercentage ) ; } } ) ( ) ; }