From 14b92ee998d529bdd4191040d1e3ebe66d450498 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 20 Mar 2021 22:20:40 +0200 Subject: [PATCH 1/3] Diff any dimensions suggested --- .env | 1 + src/test-runs/test-runs.service.spec.ts | 87 +++++++++++++++---------- src/test-runs/test-runs.service.ts | 78 +++++++++++----------- 3 files changed, 93 insertions(+), 73 deletions(-) diff --git a/.env b/.env index bdab8951..fd1fd4a1 100644 --- a/.env +++ b/.env @@ -18,3 +18,4 @@ POSTGRES_DB=vrt_db_dev # features AUTO_APPROVE_BASED_ON_HISTORY=true +ALLOW_DIFF_DIMENSIONS=true diff --git a/src/test-runs/test-runs.service.spec.ts b/src/test-runs/test-runs.service.spec.ts index 3056b94c..7d045b8c 100644 --- a/src/test-runs/test-runs.service.spec.ts +++ b/src/test-runs/test-runs.service.spec.ts @@ -5,6 +5,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { StaticService } from '../shared/static/static.service'; import { PNG } from 'pngjs'; import { TestStatus, TestRun, TestVariation } from '@prisma/client'; +import Pixelmatch from 'pixelmatch'; import { CreateTestRequestDto } from './dto/create-test-request.dto'; import { TestRunResultDto } from './dto/testRunResult.dto'; import { DiffResult } from './diffResult'; @@ -15,7 +16,7 @@ import { convertBaselineDataToQuery } from '../shared/dto/baseline-data.dto'; import { TestRunDto } from './dto/testRun.dto'; import { BuildsService } from '../builds/builds.service'; -// jest.mock('pixelmatch'); +jest.mock('pixelmatch'); jest.mock('./dto/testRunResult.dto'); const initService = async ({ @@ -98,22 +99,6 @@ const initService = async ({ return module.get(TestRunsService); }; - -// Helper fills PNG with specified color, or fills number of pixels in png if specified -const fillPng = (png, r, g, b, pixelsCount = 0, alpha = 255) => { - for (let y = 0; y < png.height; y++) { - for (let x = 0; x < png.width; x++) { - const idx = (png.width * y + x) << 2; - if (pixelsCount === 0 || idx < pixelsCount * 4) { - png.data[idx] = r; - png.data[idx + 1] = g; - png.data[idx + 2] = b; - png.data[idx + 3] = alpha; - } - } - } -}; - describe('TestRunsService', () => { let service: TestRunsService; const ignoreAreas = [{ x: 1, y: 2, width: 10, height: 20 }]; @@ -618,7 +603,8 @@ describe('TestRunsService', () => { }); }); - it('diff image dimensions mismatch ', async () => { + it('diff image dimensions mismatch', async () => { + delete process.env.ALLOW_DIFF_DIMENSIONS; const baseline = new PNG({ width: 10, height: 10, @@ -627,21 +613,60 @@ describe('TestRunsService', () => { width: 20, height: 20, }); - fillPng(baseline, 0, 0, 0); - fillPng(image, 0, 0, 0); + service = await initService({}); + const result = service.getDiff(baseline, image, baseTestRun); + + expect(result).toStrictEqual({ + status: TestStatus.unresolved, + diffName: null, + pixelMisMatchCount: undefined, + diffPercent: undefined, + isSameDimension: false, + }); + }); + + it('diff image dimensions mismatch ALLOWED', async () => { + process.env.ALLOW_DIFF_DIMENSIONS = 'true'; + const baseline = new PNG({ + width: 20, + height: 10, + }); + const image = new PNG({ + width: 10, + height: 20, + }); const diffName = 'diff name'; const saveImageMock = jest.fn().mockReturnValueOnce(diffName); + mocked(Pixelmatch).mockReturnValueOnce(200); service = await initService({ saveImageMock }); const result = service.getDiff(baseline, image, baseTestRun); - expect(saveImageMock).toHaveBeenCalledTimes(1); + expect(mocked(Pixelmatch)).toHaveBeenCalledWith( + new PNG({ + width: 20, + height: 20, + }).data, + new PNG({ + width: 20, + height: 20, + }).data, + new PNG({ + width: 20, + height: 20, + }).data, + 20, + 20, + { + includeAA: true, + } + ); expect(result).toStrictEqual({ status: TestStatus.unresolved, diffName, - pixelMisMatchCount: 20 * 20 - 10 * 10, - diffPercent: 75, + pixelMisMatchCount: 200, + diffPercent: 50, isSameDimension: false, }); }); @@ -655,9 +680,8 @@ describe('TestRunsService', () => { width: 10, height: 10, }); - fillPng(baseline, 0, 0, 0); - fillPng(image, 0, 0, 0); service = await initService({}); + mocked(Pixelmatch).mockReturnValueOnce(0); const result = service.getDiff(baseline, image, baseTestRun); @@ -685,14 +709,10 @@ describe('TestRunsService', () => { width: 100, height: 100, }); - fillPng(baseline, 0, 0, 0); - fillPng(image, 0, 0, 0); - - const pixelMisMatchCount = 150; - fillPng(image, 255, 0, 0, pixelMisMatchCount); - const saveImageMock = jest.fn(); service = await initService({ saveImageMock }); + const pixelMisMatchCount = 150; + mocked(Pixelmatch).mockReturnValueOnce(pixelMisMatchCount); const result = service.getDiff(baseline, image, testRun); @@ -721,11 +741,8 @@ describe('TestRunsService', () => { width: 100, height: 100, }); - fillPng(baseline, 0, 0, 0, 255); - fillPng(image, 0, 0, 0, 255); const pixelMisMatchCount = 200; - fillPng(image, 255, 0, 0, pixelMisMatchCount); - + mocked(Pixelmatch).mockReturnValueOnce(pixelMisMatchCount); const diffName = 'diff name'; const saveImageMock = jest.fn().mockReturnValueOnce(diffName); service = await initService({ diff --git a/src/test-runs/test-runs.service.ts b/src/test-runs/test-runs.service.ts index 6c7a5852..f99fa844 100644 --- a/src/test-runs/test-runs.service.ts +++ b/src/test-runs/test-runs.service.ts @@ -271,10 +271,10 @@ export class TestRunsService { }); } - prepareImage(image: PNG, width: number, height: number) { + private scaleImageToSize(image: PNG, width: number, height: number): PNG { if (width > image.width || height > image.height) { const preparedImage = new PNG({ width, height, fill: true }); - PNG.bitblt(image, preparedImage, 0, 0, image.width, image.height, 0, 0); + PNG.bitblt(image, preparedImage, 0, 0, image.width, image.height); return preparedImage; } else { return image; @@ -290,46 +290,48 @@ export class TestRunsService { isSameDimension: undefined, }; - if (baseline) { - result.isSameDimension = baseline.width === image.width && baseline.height === image.height; + if (!baseline) { + // no baseline + return result; + } - const width = Math.max(baseline.width, image.width); - const height = Math.max(baseline.height, image.height); + result.isSameDimension = baseline.width === image.width && baseline.height === image.height; - const prepearedBaseline = this.prepareImage(baseline, width, height); - const preparedImage = this.prepareImage(image, width, height); + if (!result.isSameDimension && !process.env.ALLOW_DIFF_DIMENSIONS) { + // diff dimensions + result.status = TestStatus.unresolved; + return result; + } - // if (result.isSameDimension) { - const diff = new PNG({ - width, - height, - }); + const maxWidth = Math.max(baseline.width, image.width); + const maxHeight = Math.max(baseline.height, image.height); - const ignoreAreas = this.getIgnoteAreas(testRun); - // compare - result.pixelMisMatchCount = Pixelmatch( - this.applyIgnoreAreas(prepearedBaseline, ignoreAreas), - this.applyIgnoreAreas(preparedImage, ignoreAreas), - diff.data, - width, - height, - { - includeAA: true, - } - ); - result.diffPercent = (result.pixelMisMatchCount * 100) / (image.width * image.height); - - if (result.diffPercent > testRun.diffTollerancePercent) { - // save diff - result.diffName = this.staticService.saveImage('diff', PNG.sync.write(diff)); - result.status = TestStatus.unresolved; - } else { - result.status = TestStatus.ok; - } - // } else { - // // diff dimensions - // result.status = TestStatus.unresolved; - // } + // scale to image to size + const scaledBaseline = this.scaleImageToSize(baseline, maxWidth, maxHeight); + const scaledImage = this.scaleImageToSize(image, maxWidth, maxHeight); + + // apply ignore areas + const ignoreAreas = this.getIgnoteAreas(testRun); + const baselineData = this.applyIgnoreAreas(scaledBaseline, ignoreAreas); + const imageData = this.applyIgnoreAreas(scaledImage, ignoreAreas); + + // compare + const diff = new PNG({ + width: maxWidth, + height: maxHeight, + }); + result.pixelMisMatchCount = Pixelmatch(baselineData, imageData, diff.data, maxWidth, maxHeight, { + includeAA: true, + }); + result.diffPercent = (result.pixelMisMatchCount * 100) / (scaledImage.width * scaledImage.height); + + // process result + if (result.diffPercent > testRun.diffTollerancePercent) { + // save diff + result.diffName = this.staticService.saveImage('diff', PNG.sync.write(diff)); + result.status = TestStatus.unresolved; + } else { + result.status = TestStatus.ok; } return result; From d6edd2663a9eb505622b3cc4658359af3d99a7f9 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 20 Mar 2021 22:23:56 +0200 Subject: [PATCH 2/3] Update test-runs.service.spec.ts --- src/test-runs/test-runs.service.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test-runs/test-runs.service.spec.ts b/src/test-runs/test-runs.service.spec.ts index 7d045b8c..5bfbd55b 100644 --- a/src/test-runs/test-runs.service.spec.ts +++ b/src/test-runs/test-runs.service.spec.ts @@ -662,6 +662,7 @@ describe('TestRunsService', () => { includeAA: true, } ); + expect(saveImageMock).toHaveBeenCalledTimes(1); expect(result).toStrictEqual({ status: TestStatus.unresolved, diffName, From 1d45a941b299babd3223cb51b1e1212e1a269ada Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 20 Mar 2021 22:38:42 +0200 Subject: [PATCH 3/3] Update test-runs.service.ts --- src/test-runs/test-runs.service.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/test-runs/test-runs.service.ts b/src/test-runs/test-runs.service.ts index f99fa844..20a33bb3 100644 --- a/src/test-runs/test-runs.service.ts +++ b/src/test-runs/test-runs.service.ts @@ -276,9 +276,8 @@ export class TestRunsService { const preparedImage = new PNG({ width, height, fill: true }); PNG.bitblt(image, preparedImage, 0, 0, image.width, image.height); return preparedImage; - } else { - return image; } + return image; } getDiff(baseline: PNG, image: PNG, testRun: TestRun): DiffResult { @@ -302,11 +301,9 @@ export class TestRunsService { result.status = TestStatus.unresolved; return result; } - + // scale image to max size const maxWidth = Math.max(baseline.width, image.width); const maxHeight = Math.max(baseline.height, image.height); - - // scale to image to size const scaledBaseline = this.scaleImageToSize(baseline, maxWidth, maxHeight); const scaledImage = this.scaleImageToSize(image, maxWidth, maxHeight); @@ -333,7 +330,6 @@ export class TestRunsService { } else { result.status = TestStatus.ok; } - return result; }