Skip to content

Commit f87a989

Browse files
committed
feat: introduce imagesPath option (#152)
allow relative path resolution create special {spec_path} variable allow absolute path resolution (under unix and win systems) refactor afterScreenshot hook to awaits add deprecation message for imagesDir option add docs info references issue #147 BREAKING CHANGE: deprecate imagesDir option in favor of imagesPath - see docs for additional information
1 parent 38679a7 commit f87a989

File tree

9 files changed

+243
-135
lines changed

9 files changed

+243
-135
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,13 @@ cy.matchImage({
174174
// default: false
175175
updateImages: true,
176176
// directory path in which screenshot images will be stored
177-
// image visualiser will normalise path separators depending on OS it's being run within, so always use / for nested paths
178-
// default: '__image_snapshots__'
179-
imagesDir: 'this-might-be-your-custom/maybe-nested-directory',
177+
// relative path are resolved against project root
178+
// absolute paths (both on unix and windows OS) supported
179+
// path separators will be normalised by the plugin depending on OS, you should always use / as path separator, e.g.: C:/my-directory/nested for windows-like drive notation
180+
// There are one special variable available to be used in the path:
181+
// - {spec_path} - relative path leading from project root to the current spec file directory (e.g. `/src/components/my-tested-component`)
182+
// default: '{spec_path}/__image_snapshots__'
183+
imagesPath: 'this-might-be-your-custom/maybe-nested-directory',
180184
// maximum threshold above which the test should fail
181185
// default: 0.01
182186
maxDiffThreshold: 0.1,

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"scripts": {
66
"serve": "vue-cli-service serve",
77
"build": "vue-cli-service build",
8-
"test:open": "vue-cli-service test:e2e --env \"pluginVisualRegressionImagesDir=__image_snapshots_local__\"",
8+
"test:open": "vue-cli-service test:e2e --env \"pluginVisualRegressionImagesPath={spec_path}/__image_snapshots_local__\"",
99
"test:run": "vue-cli-service test:e2e",
1010
"test:ct": "yarn test:open --component",
1111
"test:ct:ci": "yarn test:run --component --headless",

src/afterScreenshot.hook.test.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { it, expect, describe } from "vitest";
22
import path from "path";
33
import { promises as fs, existsSync } from "fs";
4-
import { initAfterScreenshotHook } from "./afterScreenshot.hook";
4+
import {
5+
initAfterScreenshotHook,
6+
parseAbsolutePath,
7+
} from "./afterScreenshot.hook";
58
import { dir, file, setGracefulCleanup } from "tmp-promise";
6-
import { IMAGE_SNAPSHOT_PREFIX } from "./constants";
9+
import { IMAGE_SNAPSHOT_PREFIX, PATH_VARIABLES } from "./constants";
710

811
setGracefulCleanup();
912

@@ -31,3 +34,33 @@ describe("initAfterScreenshotHook", () => {
3134
await fs.unlink(expectedNewPath);
3235
});
3336
});
37+
38+
describe("parseAbsolutePath", () => {
39+
const projectRoot = "/its/project/root";
40+
41+
it("resolves relative paths against project root", () => {
42+
expect(
43+
parseAbsolutePath({ screenshotPath: "some/path.png", projectRoot })
44+
).toBe("/its/project/root/some/path.png");
45+
});
46+
47+
it("builds proper win paths when found", () => {
48+
expect(
49+
parseAbsolutePath({
50+
screenshotPath: `${PATH_VARIABLES.winSystemRootPath}/D/some/path.png`,
51+
projectRoot,
52+
})
53+
)
54+
// that's expected output accorind to https://stackoverflow.com/a/64135721/8805801
55+
.toBe("D:\\/some/path.png");
56+
});
57+
58+
it("resolves relative paths against project root", () => {
59+
expect(
60+
parseAbsolutePath({
61+
screenshotPath: `${PATH_VARIABLES.unixSystemRootPath}/some/path.png`,
62+
projectRoot,
63+
})
64+
).toBe("/some/path.png");
65+
});
66+
});

src/afterScreenshot.hook.ts

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import path from "path";
2-
import fs from "fs";
2+
import { promises as fs } from "fs";
33
import moveFile from "move-file";
4-
import { IMAGE_SNAPSHOT_PREFIX } from "./constants";
4+
import { IMAGE_SNAPSHOT_PREFIX, PATH_VARIABLES } from "./constants";
55

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

8+
const MIMIC_ROOT_WIN_REGEX = new RegExp(
9+
`^${PATH_VARIABLES.winSystemRootPath}\\${path.sep}([A-Z])\\${path.sep}`
10+
);
11+
const MIMIC_ROOT_UNIX_REGEX = new RegExp(
12+
`^${PATH_VARIABLES.unixSystemRootPath}\\${path.sep}`
13+
);
14+
815
const getConfigVariableOrThrow = <K extends keyof Cypress.PluginConfigOptions>(
916
config: Cypress.PluginConfigOptions,
1017
name: K
@@ -14,25 +21,33 @@ const getConfigVariableOrThrow = <K extends keyof Cypress.PluginConfigOptions>(
1421
}
1522

1623
/* c8 ignore start */
17-
throw `[Image snapshot] CypressConfig.${name} cannot be missing or \`false\`!`;
24+
throw `[@frsource/cypress-plugin-visual-regression-diff] CypressConfig.${name} cannot be missing or \`false\`!`;
1825
};
1926
/* c8 ignore stop */
2027

21-
const removeScreenshotsDirectory = (
22-
screenshotsFolder: string,
23-
onSuccess: () => void,
24-
onError: (e: Error) => void
25-
) => {
26-
fs.rm(
27-
path.join(screenshotsFolder, IMAGE_SNAPSHOT_PREFIX),
28-
{ recursive: true, force: true },
29-
(err) => {
30-
/* c8 ignore start */
31-
if (err) return onError(err);
32-
/* c8 ignore stop */
33-
onSuccess();
34-
}
35-
);
28+
export const parseAbsolutePath = ({
29+
screenshotPath,
30+
projectRoot,
31+
}: {
32+
screenshotPath: string;
33+
projectRoot: string;
34+
}) => {
35+
let newAbsolutePath: string;
36+
const matchedMimicingWinRoot = screenshotPath.match(MIMIC_ROOT_WIN_REGEX);
37+
const matchedMimicingUnixRoot = screenshotPath.match(MIMIC_ROOT_UNIX_REGEX);
38+
if (matchedMimicingWinRoot && matchedMimicingWinRoot[1]) {
39+
const driveLetter = matchedMimicingWinRoot[1];
40+
newAbsolutePath = path.join(
41+
`${driveLetter}:\\`,
42+
screenshotPath.substring(matchedMimicingWinRoot[0].length)
43+
);
44+
} else if (matchedMimicingUnixRoot) {
45+
newAbsolutePath =
46+
path.sep + screenshotPath.substring(matchedMimicingUnixRoot[0].length);
47+
} else {
48+
newAbsolutePath = path.join(projectRoot, screenshotPath);
49+
}
50+
return path.normalize(newAbsolutePath);
3651
};
3752

3853
export const initAfterScreenshotHook =
@@ -47,27 +62,25 @@ export const initAfterScreenshotHook =
4762
/* c8 ignore start */
4863
if (details.name?.indexOf(IMAGE_SNAPSHOT_PREFIX) !== 0) return;
4964
/* c8 ignore stop */
50-
return new Promise((resolve, reject) => {
51-
const screenshotsFolder = getConfigVariableOrThrow(
52-
config,
53-
"screenshotsFolder"
54-
);
65+
const screenshotsFolder = getConfigVariableOrThrow(
66+
config,
67+
"screenshotsFolder"
68+
);
69+
const screenshotPath = details.name.substring(
70+
IMAGE_SNAPSHOT_PREFIX.length + path.sep.length
71+
);
72+
const newAbsolutePath = parseAbsolutePath({
73+
screenshotPath,
74+
projectRoot: config.projectRoot,
75+
});
5576

56-
const newRelativePath = details.name.substring(
57-
IMAGE_SNAPSHOT_PREFIX.length + path.sep.length
58-
);
59-
const newAbsolutePath = path.normalize(
60-
path.join(config.projectRoot, newRelativePath)
61-
);
77+
return (async () => {
78+
await moveFile(details.path, newAbsolutePath);
79+
await fs.rm(path.join(screenshotsFolder, IMAGE_SNAPSHOT_PREFIX), {
80+
recursive: true,
81+
force: true,
82+
});
6283

63-
void moveFile(details.path, newAbsolutePath)
64-
.then(() =>
65-
removeScreenshotsDirectory(
66-
screenshotsFolder,
67-
() => resolve({ path: newAbsolutePath }),
68-
reject
69-
)
70-
)
71-
.catch(reject);
72-
});
84+
return { path: newAbsolutePath };
85+
})();
7386
};

0 commit comments

Comments
 (0)