Skip to content

Commit

Permalink
fix: autocleanup removes screenshots of other testing type
Browse files Browse the repository at this point in the history
add test case to cover this scenario
add isImageOfTestType check

resolves #178

Signed-off-by: Jakub Freisler <jakub@frsource.org>
FRSgit committed Nov 21, 2022
1 parent 5f59a2b commit c3b44a4
Showing 5 changed files with 92 additions and 41 deletions.
Binary file modified __tests__/fixtures/screenshot.actual.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/fixtures/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 33 additions & 11 deletions src/image.utils.ts
Original file line number Diff line number Diff line change
@@ -8,19 +8,41 @@ import { version } from "../package.json";
import { wasScreenshotUsed } from "./screenshotPath.utils";
import { METADATA_KEY } from "./constants";

export const addPNGMetadata = (png: Buffer) =>
addMetadata(png, METADATA_KEY, version /* c8 ignore next */);
export const getPNGMetadata = (png: Buffer) =>
getMetadata(png, METADATA_KEY /* c8 ignore next */);
type PluginMetadata = {
version: string;
testingType?: 'e2e' | 'component';
};

type PluginMetadataConfig = {
testingType?: string;
};

export const addPNGMetadata = (config: PluginMetadataConfig, png: Buffer) =>
addMetadata(png, METADATA_KEY, JSON.stringify({ version, testingType: config.testingType || 'e2e' } as PluginMetadata) /* c8 ignore next */);
export const getPNGMetadata = (png: Buffer): PluginMetadata | undefined => {
const metadataString = getMetadata(png, METADATA_KEY /* c8 ignore next */);

if (metadataString === undefined) return;
try {
return JSON.parse(metadataString);
} catch {
return { version: metadataString };
}
}
export const isImageCurrentVersion = (png: Buffer) =>
getPNGMetadata(png) === version;
getPNGMetadata(png)?.version === version;
export const isImageGeneratedByPlugin = (png: Buffer) =>
!!getPNGMetadata(png /* c8 ignore next */);
export const isImageOfTestType = (png: Buffer, testingType?: PluginMetadataConfig['testingType']) => {
if (!isImageGeneratedByPlugin(png)) return false;
const imageTestingType = getPNGMetadata(png /* c8 ignore next */)?.testingType;
return imageTestingType === testingType || testingType === imageTestingType === undefined;
};

export const writePNG = (name: string, png: PNG | Buffer) =>
export const writePNG = (config: PluginMetadataConfig, name: string, png: PNG | Buffer) =>
fs.writeFileSync(
name,
addPNGMetadata(png instanceof PNG ? PNG.sync.write(png) : png)
addPNGMetadata(config, png instanceof PNG ? PNG.sync.write(png) : png)
);

const inArea = (x: number, y: number, height: number, width: number) =>
@@ -95,17 +117,17 @@ export const alignImagesToSameSize = (
];
};

export const cleanupUnused = (rootPath: string) => {
export const cleanupUnused = (config: PluginMetadataConfig & { projectRoot: string; }) => {
glob
.sync("**/*.png", {
cwd: rootPath,
cwd: config.projectRoot,
ignore: "node_modules/**/*",
})
.forEach((pngPath) => {
const absolutePath = path.join(rootPath, pngPath);
const absolutePath = path.join(config.projectRoot, pngPath);
if (
!wasScreenshotUsed(pngPath) &&
isImageGeneratedByPlugin(fs.readFileSync(absolutePath))
isImageOfTestType(fs.readFileSync(absolutePath), config.testingType)
) {
fs.unlinkSync(absolutePath);
}
79 changes: 53 additions & 26 deletions src/task.hook.test.ts
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ describe("getScreenshotPathInfoTask", () => {
titleFromOptions: "some-title-withśpęćiał人物",
imagesPath: "nested/images/dir",
specPath,
currentRetryNumber: 0,
})
).toEqual({
screenshotPath:
@@ -62,6 +63,7 @@ describe("getScreenshotPathInfoTask", () => {
titleFromOptions: "some-title",
imagesPath: "{spec_path}/images/dir",
specPath,
currentRetryNumber: 0,
})
).toEqual({
screenshotPath:
@@ -76,6 +78,7 @@ describe("getScreenshotPathInfoTask", () => {
titleFromOptions: "some-title",
imagesPath: "/images/dir",
specPath,
currentRetryNumber: 0,
})
).toEqual({
screenshotPath:
@@ -88,6 +91,7 @@ describe("getScreenshotPathInfoTask", () => {
titleFromOptions: "some-title",
imagesPath: "C:/images/dir",
specPath,
currentRetryNumber: 0,
})
).toEqual({
screenshotPath:
@@ -104,6 +108,7 @@ describe("cleanupImagesTask", () => {
titleFromOptions: "some-file",
imagesPath: "images",
specPath: "some/spec/path",
currentRetryNumber: 0,
});
return path.join(
projectRoot,
@@ -113,34 +118,56 @@ describe("cleanupImagesTask", () => {
);
};

it("does not remove used screenshot", async () => {
const { path: projectRoot } = await dir();
const screenshotPath = await writeTmpFixture(
await generateUsedScreenshotPath(projectRoot),
oldImgFixture
);
describe('when testing type does not match', () => {
it("does not remove unused screenshot", async () => {
const { path: projectRoot } = await dir();
const screenshotPath = await writeTmpFixture(
path.join(projectRoot, "some-file-2 #0.png"),
oldImgFixture
);

cleanupImagesTask({
projectRoot,
env: { pluginVisualRegressionCleanupUnusedImages: true },
} as unknown as Cypress.PluginConfigOptions);
cleanupImagesTask({
projectRoot,
env: { pluginVisualRegressionCleanupUnusedImages: true },
testingType: 'component',
} as unknown as Cypress.PluginConfigOptions);

expect(existsSync(screenshotPath)).toBe(true);
expect(existsSync(screenshotPath)).toBe(true);
});
});

it("removes unused screenshot", async () => {
const { path: projectRoot } = await dir();
const screenshotPath = await writeTmpFixture(
path.join(projectRoot, "some-file-2 #0.png"),
oldImgFixture
);
describe('when testing type matches', () => {
it("does not remove used screenshot", async () => {
const { path: projectRoot } = await dir();
const screenshotPath = await writeTmpFixture(
await generateUsedScreenshotPath(projectRoot),
oldImgFixture
);

cleanupImagesTask({
projectRoot,
env: { pluginVisualRegressionCleanupUnusedImages: true },
} as unknown as Cypress.PluginConfigOptions);
cleanupImagesTask({
projectRoot,
env: { pluginVisualRegressionCleanupUnusedImages: true },
testingType: 'e2e',
} as unknown as Cypress.PluginConfigOptions);

expect(existsSync(screenshotPath)).toBe(false);
expect(existsSync(screenshotPath)).toBe(true);
});

it("removes unused screenshot", async () => {
const { path: projectRoot } = await dir();
const screenshotPath = await writeTmpFixture(
path.join(projectRoot, "some-file-2 #0.png"),
oldImgFixture
);

cleanupImagesTask({
projectRoot,
env: { pluginVisualRegressionCleanupUnusedImages: true },
testingType: 'e2e',
} as unknown as Cypress.PluginConfigOptions);

expect(existsSync(screenshotPath)).toBe(false);
});
});
});
});
@@ -178,7 +205,7 @@ describe("compareImagesTask", () => {
describe("when old screenshot exists", () => {
it("resolves with a success message", async () =>
expect(
compareImagesTask(await generateConfig({ updateImages: true }))
compareImagesTask({ testingType: 'e2e' }, await generateConfig({ updateImages: true }))
).resolves.toEqual({
message:
"Image diff factor (0%) is within boundaries of maximum threshold option 0.5.",
@@ -197,7 +224,7 @@ describe("compareImagesTask", () => {
const cfg = await generateConfig({ updateImages: false });
await fs.unlink(cfg.imgOld);

await expect(compareImagesTask(cfg)).resolves.toEqual({
await expect(compareImagesTask({ testingType: 'e2e' }, cfg)).resolves.toEqual({
message:
"Image diff factor (0%) is within boundaries of maximum threshold option 0.5.",
imgDiff: 0,
@@ -214,7 +241,7 @@ describe("compareImagesTask", () => {
it("resolves with an error message", async () => {
const cfg = await generateConfig({ updateImages: false });

await expect(compareImagesTask(cfg)).resolves.toMatchSnapshot();
await expect(compareImagesTask({ testingType: 'e2e' }, cfg)).resolves.toMatchSnapshot();
});
});

@@ -223,7 +250,7 @@ describe("compareImagesTask", () => {
const cfg = await generateConfig({ updateImages: false });
await writeTmpFixture(cfg.imgNew, oldImgFixture);

await expect(compareImagesTask(cfg)).resolves.toMatchSnapshot();
await expect(compareImagesTask({ testingType: 'e2e' }, cfg)).resolves.toMatchSnapshot();
});
});
});
10 changes: 6 additions & 4 deletions src/task.hook.ts
Original file line number Diff line number Diff line change
@@ -47,7 +47,7 @@ export const getScreenshotPathInfoTask = (cfg: {

export const cleanupImagesTask = (config: Cypress.PluginConfigOptions) => {
if (config.env["pluginVisualRegressionCleanupUnusedImages"]) {
cleanupUnused(config.projectRoot);
cleanupUnused(config);
}

resetScreenshotNameCache();
@@ -68,6 +68,7 @@ export const approveImageTask = ({ img }: { img: string }) => {
};

export const compareImagesTask = async (
cypressConfig: { testingType: string },
cfg: CompareImagesCfg
): Promise<CompareImagesTaskReturn> => {
const messages = [] as string[];
@@ -127,6 +128,7 @@ export const compareImagesTask = async (

if (error) {
writePNG(
cypressConfig,
cfg.imgNew.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff),
diffBuffer
);
@@ -141,7 +143,7 @@ export const compareImagesTask = async (
};
} else {
if (rawImgOld && !isImageCurrentVersion(rawImgOldBuffer)) {
writePNG(cfg.imgNew, rawImgNewBuffer);
writePNG(cypressConfig, cfg.imgNew, rawImgNewBuffer);
moveFile.sync(cfg.imgNew, cfg.imgOld);
} else {
// don't overwrite file if it's the same (imgDiff < cfg.maxDiffThreshold && !isImgSizeDifferent)
@@ -154,7 +156,7 @@ export const compareImagesTask = async (
imgNewBase64 = "";
imgDiffBase64 = "";
imgOldBase64 = "";
writePNG(cfg.imgNew, rawImgNewBuffer);
writePNG(cypressConfig, cfg.imgNew, rawImgNewBuffer);
moveFile.sync(cfg.imgNew, cfg.imgOld);
}

@@ -189,6 +191,6 @@ export const initTaskHook = (config: Cypress.PluginConfigOptions) => ({
[TASK.cleanupImages]: cleanupImagesTask.bind(undefined, config),
[TASK.doesFileExist]: doesFileExistTask,
[TASK.approveImage]: approveImageTask,
[TASK.compareImages]: compareImagesTask,
[TASK.compareImages]: compareImagesTask.bind(undefined, config),
});
/* c8 ignore stop */

0 comments on commit c3b44a4

Please sign in to comment.