From 1130eb4816729691ae7e7ebc3feefa089debf6b9 Mon Sep 17 00:00:00 2001 From: Feoktist Shovchko Date: Thu, 19 Sep 2024 16:12:43 +0300 Subject: [PATCH 1/2] test(e2e): handle sanpshot's size mismatch --- .../serializers/image-snapshot.matcher.ts | 27 +++++++++------ .../serializers/image-snapshot.pocessor.ts | 14 +++++--- e2e/setup/serializers/image-snapshot.sharp.ts | 34 +++++++++++++++++++ 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/e2e/setup/serializers/image-snapshot.matcher.ts b/e2e/setup/serializers/image-snapshot.matcher.ts index 21ce33908..3458f2778 100644 --- a/e2e/setup/serializers/image-snapshot.matcher.ts +++ b/e2e/setup/serializers/image-snapshot.matcher.ts @@ -7,6 +7,13 @@ import type {SnapshotData} from './image-snapshot.pocessor'; export type SnapshotMatcherOptions = pixelmatch.PixelmatchOptions; +type ImagesCompareResult = { + reason: 'content'; +} | { + reason: 'error'; + message: any; +} | undefined; + export class SnapshotMatcher { protected static readonly MIN_DIFF_THRESHOLD: number = 0.0001; @@ -25,7 +32,7 @@ export class SnapshotMatcher { } public async match(): Promise { - const {current, previous, snapshotPath} = this.received; + const {current, previous, snapshotPath, diffPath} = this.received; if (!previous.buffer) { mkDir(path.dirname(snapshotPath)); current.img.toFile(snapshotPath); @@ -34,31 +41,31 @@ export class SnapshotMatcher { const diff = await this.compareImages(); if (!diff) return this.getMatcherResult(true, `Image is the same as the snapshot: ${snapshotPath}`); - if (diff.reason === 'content') return this.getMatcherResult(false, `Image mismatch found: ${diff.path}`); - return this.getMatcherResult(false, `Error comparing snapshot to image ${snapshotPath}`); + if (diff.reason === 'content') return this.getMatcherResult(false, `Image mismatch found: ${diffPath}`); + return this.getMatcherResult(false, `Error comparing snapshot to image ${snapshotPath}\nMessage: ${diff.message}`); } protected getMatcherResult(pass: boolean, message: string): jest.CustomMatcherResult { return {pass, message: () => message}; } - protected async compareImages(): Promise<{reason: 'content' | 'error', path?: string} | undefined> { + protected async compareImages(): Promise { const {current, previous, diffPath} = this.received; - const prevImg = previous.buffer!; - const currImg = current.buffer; + const prevBuffer = previous.buffer!; + const currBuffer = current.buffer; - const {width, height} = prevImg.info; + const {width, height} = prevBuffer.info; const diffBuffer = Buffer.alloc(width * height * 4); try { - const numDiffPixel = pixelmatch(prevImg.data, currImg.data, diffBuffer, width, height, this.config); + const numDiffPixel = pixelmatch(prevBuffer.data, currBuffer.data, diffBuffer, width, height, this.config); if (numDiffPixel > width * height * SnapshotMatcher.MIN_DIFF_THRESHOLD) { const diffConfig = Object.assign({}, this.received, {diffBuffer}); mkDir(path.dirname(diffPath)); await (await DiffImageComposer.compose(diffConfig)).toFile(diffPath); - return {reason: 'content', path: diffPath}; + return {reason: 'content'}; } } catch (e) { - return {reason: 'error'}; + return {reason: 'error', message: e}; } } } diff --git a/e2e/setup/serializers/image-snapshot.pocessor.ts b/e2e/setup/serializers/image-snapshot.pocessor.ts index 8e9d08251..2655d20f3 100644 --- a/e2e/setup/serializers/image-snapshot.pocessor.ts +++ b/e2e/setup/serializers/image-snapshot.pocessor.ts @@ -32,18 +32,24 @@ export class SnapshotDataProcessor { const diffPath = path.join(diffDir, buildSnapshotName(currentTestName!, 'diff')); const shouldUpdate = !fs.existsSync(snapshotPath) || context.snapshotState._updateSnapshot === 'all'; - const recievedJPG = SharpService.toJPEG(received); + const [currentBuffer, previousBuffer] = shouldUpdate + ? [received, undefined] + : await SharpService.normalizeImages(received, snapshotPath); + + const currentJPG = SharpService.toJPEG(currentBuffer); + const currentRAWBuffer = await SharpService.toRawBuffered(await currentJPG.toBuffer()); + return { diffPath, snapshotPath, current: { - img: recievedJPG, - buffer: await (SharpService.toRawBuffered(await recievedJPG.toBuffer())) + img: currentJPG, + buffer: currentRAWBuffer }, previous: { - buffer: shouldUpdate ? undefined : await (SharpService.toRawBuffered(snapshotPath)) + buffer: previousBuffer ? await (SharpService.toRawBuffered(previousBuffer)) : undefined } }; } diff --git a/e2e/setup/serializers/image-snapshot.sharp.ts b/e2e/setup/serializers/image-snapshot.sharp.ts index 7b7dbe8d0..4a104dbf8 100644 --- a/e2e/setup/serializers/image-snapshot.sharp.ts +++ b/e2e/setup/serializers/image-snapshot.sharp.ts @@ -21,4 +21,38 @@ export class SharpService { public static toJPEGBufferd(input: string | Buffer, options: any = {}): Promise { return SharpService.toJPEG(input, options).toBuffer(); } + + public static async normalizeImages(img1: Buffer | string, img2: Buffer | string): Promise<[Buffer, Buffer]> { + const imgCtx1 = sharp(img1); + const imgCtx2 = sharp(img2); + + const metadata1 = await imgCtx1.metadata(); + const metadata2 = await imgCtx2.metadata(); + + const targetWidth = Math.max(metadata1.width!, metadata2.width!); + const targetHeight = Math.max(metadata1.height!, metadata2.height!); + + const padImage = async ( + image: sharp.Sharp, + width: number, + height: number, + metadata: sharp.Metadata + ): Promise => { + const padX = Math.max(0, (width - metadata.width!) / 2); + const padY = Math.max(0, (height - metadata.height!) / 2); + + return image.extend({ + top: Math.floor(padY), + bottom: Math.ceil(padY), + left: Math.floor(padX), + right: Math.ceil(padX), + background: {r: 255, g: 255, b: 255, alpha: 1} + }) + .toBuffer(); + }; + + const normalizedBuffer1 = await padImage(imgCtx1, targetWidth, targetHeight, metadata1); + const normalizedBuffer2 = await padImage(imgCtx2, targetWidth, targetHeight, metadata2); + return [normalizedBuffer1, normalizedBuffer2]; + } } From 700909b4ed22f5d05b918d84f1d7998f04f845e7 Mon Sep 17 00:00:00 2001 From: Feoktist Shovchko Date: Thu, 19 Sep 2024 16:44:52 +0300 Subject: [PATCH 2/2] chore(e2e): skip normaliztion if required size already --- e2e/setup/serializers/image-snapshot.sharp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/setup/serializers/image-snapshot.sharp.ts b/e2e/setup/serializers/image-snapshot.sharp.ts index 4a104dbf8..49e0045b9 100644 --- a/e2e/setup/serializers/image-snapshot.sharp.ts +++ b/e2e/setup/serializers/image-snapshot.sharp.ts @@ -38,6 +38,8 @@ export class SharpService { height: number, metadata: sharp.Metadata ): Promise => { + if (metadata.width === width && metadata.height === height) return image.toBuffer(); + const padX = Math.max(0, (width - metadata.width!) / 2); const padY = Math.max(0, (height - metadata.height!) / 2);