Skip to content

Commit c36f957

Browse files
committed
refactor: restructure tasks to prepare for integration tests
Signed-off-by: Jakub Freisler <[email protected]>
1 parent bde48b9 commit c36f957

File tree

6 files changed

+156
-146
lines changed

6 files changed

+156
-146
lines changed

src/commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FILE_SUFFIX, LINK_PREFIX, TASK } from "./constants";
1+
import { FILE_SUFFIX, LINK_PREFIX, TASK } from "@/constants";
22
import type pixelmatch from "pixelmatch";
33
import * as Base64 from "@frsource/base64";
44

src/image.utils.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@ import sharp from "sharp";
55
const inArea = (x: number, y: number, height: number, width: number) =>
66
y > height || x > width;
77

8-
export const fillSizeDifference =
9-
(width: number, height: number) => (image: PNG) => {
10-
for (let y = 0; y < image.height; y++) {
11-
for (let x = 0; x < image.width; x++) {
12-
if (inArea(x, y, height, width)) {
13-
const idx = (image.width * y + x) << 2;
14-
image.data[idx] = 0;
15-
image.data[idx + 1] = 0;
16-
image.data[idx + 2] = 0;
17-
image.data[idx + 3] = 64;
18-
}
8+
export const fillSizeDifference = (
9+
image: PNG,
10+
width: number,
11+
height: number
12+
) => {
13+
for (let y = 0; y < image.height; y++) {
14+
for (let x = 0; x < image.width; x++) {
15+
if (inArea(x, y, height, width)) {
16+
const idx = (image.width * y + x) << 2;
17+
image.data[idx] = 0;
18+
image.data[idx + 1] = 0;
19+
image.data[idx + 2] = 0;
20+
image.data[idx + 3] = 64;
1921
}
2022
}
21-
return image;
22-
};
23+
}
24+
return image;
25+
};
2326

2427
export const createImageResizer =
2528
(width: number, height: number) => (source: PNG) => {
@@ -61,7 +64,7 @@ export const alignImagesToSameSize = (
6164
const resizedSecond = resizeToSameSize(secondImage);
6265

6366
return [
64-
fillSizeDifference(firstImageWidth, firstImageHeight)(resizedFirst),
65-
fillSizeDifference(secondImageWidth, secondImageHeight)(resizedSecond),
67+
fillSizeDifference(resizedFirst, firstImageWidth, firstImageHeight),
68+
fillSizeDifference(resizedSecond, secondImageWidth, secondImageHeight),
6669
];
6770
};

src/plugins.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import path from "path";
22
import fs from "fs";
3-
import { IMAGE_SNAPSHOT_PREFIX } from "./constants";
43
import moveFile from "move-file";
5-
import { initTaskHooks } from "./tasks";
4+
import { IMAGE_SNAPSHOT_PREFIX } from "@/constants";
5+
import { initTasks } from "@/tasks";
66

77
type NotFalsy<T> = T extends false | null | undefined ? never : T;
88

@@ -47,11 +47,16 @@ const removeScreenshotsDirectory = (
4747
);
4848
};
4949

50-
const initAfterScreenshotHook = (
51-
on: Cypress.PluginEvents,
52-
config: Cypress.PluginConfigOptions
53-
) => {
54-
on("after:screenshot", (details) => {
50+
const initAfterScreenshotHook =
51+
(
52+
config: Cypress.PluginConfigOptions
53+
): ((
54+
details: Cypress.ScreenshotDetails
55+
) =>
56+
| void
57+
| Cypress.AfterScreenshotReturnObject
58+
| Promise<Cypress.AfterScreenshotReturnObject>) =>
59+
(details) => {
5560
if (details.name?.indexOf(IMAGE_SNAPSHOT_PREFIX) !== 0) return;
5661

5762
return new Promise((resolve, reject) => {
@@ -77,8 +82,7 @@ const initAfterScreenshotHook = (
7782
)
7883
.catch(reject);
7984
});
80-
});
81-
};
85+
};
8286

8387
export const initPlugin = (
8488
on: Cypress.PluginEvents,
@@ -88,6 +92,6 @@ export const initPlugin = (
8892
initForceDeviceScaleFactor(on);
8993
}
9094

91-
initTaskHooks(on);
92-
initAfterScreenshotHook(on, config);
95+
on("task", initTasks());
96+
on("after:screenshot", initAfterScreenshotHook(config));
9397
};

src/support.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as Base64 from "@frsource/base64";
2-
import "./commands";
3-
import { FILE_SUFFIX, LINK_PREFIX, OVERLAY_CLASS, TASK } from "./constants";
2+
import "@/commands";
3+
import { FILE_SUFFIX, LINK_PREFIX, OVERLAY_CLASS, TASK } from "@/constants";
44

55
function queueClear() {
66
(cy as unknown as { queue: { clear: () => void } }).queue.clear();

src/tasks.ts

Lines changed: 114 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import fs from "fs";
22
import path from "path";
33
import { PNG } from "pngjs";
4-
import pixelmatch from "pixelmatch";
4+
import pixelmatch, { PixelmatchOptions } from "pixelmatch";
55
import moveFile from "move-file";
66
import sanitize from "sanitize-filename";
7-
import { FILE_SUFFIX, IMAGE_SNAPSHOT_PREFIX, TASK } from "./constants";
8-
import { alignImagesToSameSize, importAndScaleImage } from "./image.utils";
7+
import { FILE_SUFFIX, IMAGE_SNAPSHOT_PREFIX, TASK } from "@/constants";
8+
import { alignImagesToSameSize, importAndScaleImage } from "@/image.utils";
99

1010
type CompareImagesCfg = {
1111
scaleFactor: number;
@@ -14,142 +14,140 @@ type CompareImagesCfg = {
1414
imgOld: string;
1515
updateImages: boolean;
1616
maxDiffThreshold: number;
17-
diffConfig: Parameters<typeof pixelmatch>[5];
18-
} & Parameters<typeof pixelmatch>[5];
17+
diffConfig: PixelmatchOptions;
18+
};
1919

2020
const round = (n: number) => Math.ceil(n * 1000) / 1000;
2121

22-
const initGetScreenshotPathTask: () => Cypress.Tasks = () => ({
23-
[TASK.getScreenshotPath]({ title, imagesDir, specPath }) {
24-
return path.join(
25-
IMAGE_SNAPSHOT_PREFIX,
26-
path.dirname(specPath),
27-
...imagesDir.split("/"),
28-
`${sanitize(title)}${FILE_SUFFIX.actual}.png`
29-
);
30-
},
31-
});
32-
3322
const unlinkSyncSafe = (path: string) =>
3423
fs.existsSync(path) && fs.unlinkSync(path);
3524
const moveSyncSafe = (pathFrom: string, pathTo: string) =>
3625
fs.existsSync(pathFrom) && moveFile.sync(pathFrom, pathTo);
3726

38-
const initApproveImageTask: () => Cypress.Tasks = () => ({
39-
[TASK.approveImage]({ img }) {
40-
const oldImg = img.replace(FILE_SUFFIX.actual, "");
41-
unlinkSyncSafe(oldImg);
27+
export const getScreenshotPathTask: Cypress.Task = ({
28+
title,
29+
imagesDir,
30+
specPath,
31+
}) =>
32+
path.join(
33+
IMAGE_SNAPSHOT_PREFIX,
34+
path.dirname(specPath),
35+
...imagesDir.split("/"),
36+
`${sanitize(title)}${FILE_SUFFIX.actual}.png`
37+
);
4238

43-
const diffImg = img.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff);
44-
unlinkSyncSafe(diffImg);
39+
export const approveImageTask = ({ img }: { img: string }) => {
40+
const oldImg = img.replace(FILE_SUFFIX.actual, "");
41+
unlinkSyncSafe(oldImg);
4542

46-
moveSyncSafe(img, oldImg);
43+
const diffImg = img.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff);
44+
unlinkSyncSafe(diffImg);
4745

48-
return null;
49-
},
50-
});
46+
moveSyncSafe(img, oldImg);
5147

52-
const initCompareImagesTask = () => ({
53-
async [TASK.compareImages](cfg: CompareImagesCfg): Promise<null | {
54-
error?: boolean;
55-
message?: string;
56-
imgDiff?: number;
57-
maxDiffThreshold?: number;
58-
}> {
59-
const messages = [] as string[];
60-
let imgDiff: number | undefined;
61-
let error = false;
62-
63-
if (fs.existsSync(cfg.imgOld) && !cfg.updateImages) {
64-
const rawImgNew = await importAndScaleImage({
65-
scaleFactor: cfg.scaleFactor,
66-
path: cfg.imgNew,
67-
});
68-
const rawImgOld = PNG.sync.read(fs.readFileSync(cfg.imgOld));
69-
const isImgSizeDifferent =
70-
rawImgNew.height !== rawImgOld.height ||
71-
rawImgNew.width !== rawImgOld.width;
72-
73-
const [imgNew, imgOld] = isImgSizeDifferent
74-
? alignImagesToSameSize(rawImgNew, rawImgOld)
75-
: [rawImgNew, rawImgOld];
76-
77-
const { width, height } = imgNew;
78-
const diff = new PNG({ width, height });
79-
const diffConfig = Object.assign({ includeAA: true }, cfg.diffConfig);
80-
81-
const diffPixels = pixelmatch(
82-
imgNew.data,
83-
imgOld.data,
84-
diff.data,
85-
width,
86-
height,
87-
diffConfig
48+
return null;
49+
};
50+
51+
export const compareImagesTask = async (
52+
cfg: CompareImagesCfg
53+
): Promise<null | {
54+
error?: boolean;
55+
message?: string;
56+
imgDiff?: number;
57+
maxDiffThreshold?: number;
58+
}> => {
59+
const messages = [] as string[];
60+
let imgDiff: number | undefined;
61+
let error = false;
62+
63+
if (fs.existsSync(cfg.imgOld) && !cfg.updateImages) {
64+
const rawImgNew = await importAndScaleImage({
65+
scaleFactor: cfg.scaleFactor,
66+
path: cfg.imgNew,
67+
});
68+
const rawImgOld = PNG.sync.read(fs.readFileSync(cfg.imgOld));
69+
const isImgSizeDifferent =
70+
rawImgNew.height !== rawImgOld.height ||
71+
rawImgNew.width !== rawImgOld.width;
72+
73+
const [imgNew, imgOld] = isImgSizeDifferent
74+
? alignImagesToSameSize(rawImgNew, rawImgOld)
75+
: [rawImgNew, rawImgOld];
76+
77+
const { width, height } = imgNew;
78+
const diff = new PNG({ width, height });
79+
const diffConfig = Object.assign({ includeAA: true }, cfg.diffConfig);
80+
81+
const diffPixels = pixelmatch(
82+
imgNew.data,
83+
imgOld.data,
84+
diff.data,
85+
width,
86+
height,
87+
diffConfig
88+
);
89+
imgDiff = diffPixels / (width * height);
90+
91+
if (isImgSizeDifferent) {
92+
messages.push(
93+
`Warning: Images size mismatch - new screenshot is ${rawImgNew.width}px by ${rawImgNew.height}px while old one is ${rawImgOld.width}px by ${rawImgOld.height} (width x height).`
8894
);
89-
imgDiff = diffPixels / (width * height);
90-
91-
if (isImgSizeDifferent) {
92-
messages.push(
93-
`Warning: Images size mismatch - new screenshot is ${rawImgNew.width}px by ${rawImgNew.height}px while old one is ${rawImgOld.width}px by ${rawImgOld.height} (width x height).`
94-
);
95-
}
96-
97-
if (imgDiff > cfg.maxDiffThreshold) {
98-
messages.unshift(
99-
`Image diff factor (${round(
100-
imgDiff
101-
)}) is bigger than maximum threshold option ${cfg.maxDiffThreshold}.`
102-
);
103-
error = true;
104-
}
105-
106-
if (error) {
107-
fs.writeFileSync(
108-
cfg.imgNew.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff),
109-
PNG.sync.write(diff)
110-
);
111-
return {
112-
error,
113-
message: messages.join("\n"),
114-
imgDiff,
115-
maxDiffThreshold: cfg.maxDiffThreshold,
116-
};
117-
} else {
118-
// don't overwrite file if it's the same (imgDiff < cfg.maxDiffThreshold && !isImgSizeDifferent)
119-
fs.unlinkSync(cfg.imgNew);
120-
}
121-
} else {
122-
// there is no "old screenshot" or screenshots should be immediately updated
123-
imgDiff = 0;
124-
moveFile.sync(cfg.imgNew, cfg.imgOld);
12595
}
12696

127-
if (typeof imgDiff !== "undefined") {
97+
if (imgDiff > cfg.maxDiffThreshold) {
12898
messages.unshift(
129-
`Image diff (${round(
99+
`Image diff factor (${round(
130100
imgDiff
131-
)}%) is within boundaries of maximum threshold option ${
132-
cfg.maxDiffThreshold
133-
}.`
101+
)}) is bigger than maximum threshold option ${cfg.maxDiffThreshold}.`
102+
);
103+
error = true;
104+
}
105+
106+
if (error) {
107+
fs.writeFileSync(
108+
cfg.imgNew.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff),
109+
PNG.sync.write(diff)
134110
);
135111
return {
112+
error,
136113
message: messages.join("\n"),
137114
imgDiff,
138115
maxDiffThreshold: cfg.maxDiffThreshold,
139116
};
117+
} else {
118+
// don't overwrite file if it's the same (imgDiff < cfg.maxDiffThreshold && !isImgSizeDifferent)
119+
fs.unlinkSync(cfg.imgNew);
140120
}
121+
} else {
122+
// there is no "old screenshot" or screenshots should be immediately updated
123+
imgDiff = 0;
124+
moveFile.sync(cfg.imgNew, cfg.imgOld);
125+
}
126+
127+
if (typeof imgDiff !== "undefined") {
128+
messages.unshift(
129+
`Image diff (${round(
130+
imgDiff
131+
)}%) is within boundaries of maximum threshold option ${
132+
cfg.maxDiffThreshold
133+
}.`
134+
);
135+
return {
136+
message: messages.join("\n"),
137+
imgDiff,
138+
maxDiffThreshold: cfg.maxDiffThreshold,
139+
};
140+
}
141+
142+
return null;
143+
};
141144

142-
return null;
143-
},
144-
});
145+
export const doesFileExistTask: Cypress.Task = ({ path }) =>
146+
fs.existsSync(path);
145147

146-
export const initTaskHooks = (on: Cypress.PluginEvents) => {
147-
on("task", {
148-
...initGetScreenshotPathTask(),
149-
[TASK.doesFileExist]({ path }) {
150-
return fs.existsSync(path);
151-
},
152-
...initApproveImageTask(),
153-
...initCompareImagesTask(),
154-
});
155-
};
148+
export const initTasks = () => ({
149+
[TASK.getScreenshotPath]: getScreenshotPathTask,
150+
[TASK.doesFileExist]: doesFileExistTask,
151+
[TASK.approveImage]: approveImageTask,
152+
[TASK.compareImages]: compareImagesTask,
153+
});

tsconfig.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
"lib": ["es5", "dom"],
55
"types": ["cypress"],
66
"strict": true,
7-
"esModuleInterop": true
7+
"esModuleInterop": true,
8+
"baseUrl": ".",
9+
"paths": {
10+
"@/*": ["src/*"],
11+
"@fixtures/*": ["src/__tests__/partials/*"]
12+
}
813
},
914
"include": ["src/*.ts"]
1015
}

0 commit comments

Comments
 (0)