Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(e2e): handle sanpshot's size mismatch #2670

Merged
merged 2 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions e2e/setup/serializers/image-snapshot.matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -25,7 +32,7 @@ export class SnapshotMatcher {
}

public async match(): Promise<jest.CustomMatcherResult> {
const {current, previous, snapshotPath} = this.received;
const {current, previous, snapshotPath, diffPath} = this.received;
if (!previous.buffer) {
mkDir(path.dirname(snapshotPath));
current.img.toFile(snapshotPath);
Expand All @@ -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<ImagesCompareResult> {
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};
}
}
}
14 changes: 10 additions & 4 deletions e2e/setup/serializers/image-snapshot.pocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};
}
Expand Down
36 changes: 36 additions & 0 deletions e2e/setup/serializers/image-snapshot.sharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,40 @@ export class SharpService {
public static toJPEGBufferd(input: string | Buffer, options: any = {}): Promise<Buffer> {
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 (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate method instead of anonymous function

image: sharp.Sharp,
width: number,
height: number,
metadata: sharp.Metadata
): Promise<Buffer> => {
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);

return image.extend({
top: Math.floor(padY),
bottom: Math.ceil(padY),
left: Math.floor(padX),
right: Math.ceil(padX),
Comment on lines +47 to +50
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Align to top/left

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];
}
}