Skip to content

Commit 429915b

Browse files
committed
test: add integration tests & coverage
use vitest send coverage to codeclimate on CI extract afterScreenshot hook to separate file refactor commands a bit Signed-off-by: Jakub Freisler <[email protected]>
1 parent c36f957 commit 429915b

20 files changed

+1263
-138
lines changed

.github/workflows/ci.yml

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,41 @@ jobs:
102102
- name: Test component-testing
103103
run: yarn test:ct:ci
104104

105+
test-integration-coverage:
106+
name: test-integration-coverage
107+
runs-on: ubuntu-latest
108+
needs: cache
109+
steps:
110+
- uses: actions/checkout@v3
111+
- uses: actions/setup-node@v3
112+
with:
113+
node-version: '16.x'
114+
- name: remove git auth
115+
run: git config --unset http.https://github.com/.extraheader
116+
- name: Configure Yarn cache
117+
uses: actions/cache@v3
118+
with:
119+
key: ${{ needs.cache.outputs.yarn-cache-key }}
120+
path: |
121+
~/.cache/Cypress
122+
.yarn
123+
node_modules
124+
example/node_modules
125+
- name: Install dependencies
126+
run: yarn --immutable
127+
- name: Test integration (with coverage)
128+
uses: paambaati/codeclimate-action@84cea27117a473d605400ca3a97fcef7e433e2d6
129+
env:
130+
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
131+
with:
132+
coverageCommand: yarn test:integration:ci
133+
coverageLocations: |
134+
${{github.workspace}}/coverage/lcov.info:lcov
135+
105136
build-and-release:
106137
name: build and release
107138
runs-on: ubuntu-latest
108-
needs: [cache, lint, test]
139+
needs: [cache, lint, test, test-integration-coverage]
109140
if: ${{ github.actor != 'dependabot[bot]' && github.ref == 'refs/heads/main' && github.event_name == 'push' }}
110141
steps:
111142
- uses: actions/checkout@v3

example/yarn.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,18 @@ __metadata:
133133
languageName: node
134134
linkType: hard
135135

136-
"@frsource/base64@npm:^1.0.1":
137-
version: 1.0.1
138-
resolution: "@frsource/base64@npm:1.0.1"
139-
checksum: ea1b772d0cf384c861bdbaa2be49f3bab172906434255669632fdfbf36c67d2a4ed83b0adb6ca670c6664ee2a163551dde784820951aff55fa0020ece171c7ed
136+
"@frsource/base64@npm:1.0.2":
137+
version: 1.0.2
138+
resolution: "@frsource/base64@npm:1.0.2"
139+
checksum: b979a6af4b0e9e205690a991c34c6d34afcdfa2f3de82ce5943b44987d51f2493b6ea47848dae3ad29679ef40f1065a226161b4fd8832c9cb4c8cd6046af361e
140140
languageName: node
141141
linkType: hard
142142

143143
"@frsource/cypress-plugin-visual-regression-diff@portal:..::locator=example%40workspace%3A.":
144144
version: 0.0.0-use.local
145145
resolution: "@frsource/cypress-plugin-visual-regression-diff@portal:..::locator=example%40workspace%3A."
146146
dependencies:
147-
"@frsource/base64": ^1.0.1
147+
"@frsource/base64": 1.0.2
148148
move-file: 2.1.0
149149
pixelmatch: 5.3.0
150150
pngjs: 6.0.0

package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@
4646
"release:test": "yarn release --no-git-tag-version --no-push --skip-npm",
4747
"cypress:ct": "cypress open-ct",
4848
"cypress:ct:headless": "cypress run-ct --browser chrome --headless",
49-
"test": "cd example && yarn test:ct:ci && yarn test:e2e:ci",
49+
"test": "cd example && yarn test:ct:ci && yarn test:e2e:ci && yarn test:integration:ci",
50+
"test:integration": "vitest",
51+
"test:integration:coverage": "vitest --coverage",
52+
"test:integration:ci": "CI=true vitest run --coverage",
5053
"test:ct": "cd example && yarn test:ct",
5154
"test:ct:ci": "cd example && yarn test:ct:ci",
5255
"test:e2e": "cd example && yarn test:e2e",
@@ -68,8 +71,10 @@
6871
"@types/pixelmatch": "5.2.4",
6972
"@types/pngjs": "6.0.1",
7073
"@types/sharp": "0.31.0",
74+
"@types/tmp": "^0.2.3",
7175
"@typescript-eslint/eslint-plugin": "5.38.0",
7276
"@typescript-eslint/parser": "5.38.0",
77+
"@vitest/coverage-c8": "^0.23.4",
7378
"cypress": "10.8.0",
7479
"del-cli": "5.0.0",
7580
"eslint": "8.24.0",
@@ -80,7 +85,10 @@
8085
"prettier": "2.7.1",
8186
"sanitize-filename": "1.6.3",
8287
"semantic-release": "19.0.5",
83-
"typescript": "4.8.3"
88+
"tmp-promise": "^3.0.3",
89+
"typescript": "4.8.3",
90+
"vite-tsconfig-paths": "^3.5.0",
91+
"vitest": "^0.23.4"
8492
},
8593
"keywords": [
8694
"Cypress",
@@ -97,7 +105,7 @@
97105
"Cypress image snapshot"
98106
],
99107
"dependencies": {
100-
"@frsource/base64": "^1.0.1",
108+
"@frsource/base64": "1.0.2",
101109
"move-file": "2.1.0",
102110
"pixelmatch": "5.3.0",
103111
"pngjs": "6.0.0",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Vitest Snapshot v1
2+
3+
exports[`generateOverlayTemplate > generates proper template 1`] = `
4+
"<div class=\\"cp-visual-regression-diff-overlay runner\\" style=\\"position:fixed;z-index:10;top:0;bottom:0;left:0;right:0;display:flex;flex-flow:column\\">
5+
<header style=\\"position:static\\">
6+
<nav style=\\"display:flex;width:100%;align-items:center;justify-content:space-between;padding:10px 15px;\\">
7+
<h2>some title - screenshot diff</h2>
8+
<form>
9+
<button type=\\"submit\\"><i class=\\"fa fa-check\\"></i> Update screenshot</button>
10+
<button type=\\"button\\" data-type=\\"close\\"><i class=\\"fa fa-times\\"></i> Close</button>
11+
<form>
12+
</nav>
13+
</header>
14+
<div style=\\"padding:15px;overflow:auto\\">
15+
<div style=\\"display:flex;justify-content:space-evenly;align-items:flex-start;gap:15px\\">
16+
<div
17+
style=\\"position:relative;background:#fff;border:solid 15px #fff\\"
18+
onmouseover=\\"this.querySelector('div').style.opacity=0,this.querySelector('img').style.opacity=1\\"
19+
onmouseleave=\\"this.querySelector('div').style.opacity=1,this.querySelector('img').style.opacity=0\\"
20+
>
21+
<h3>New screenshot (hover mouse away too see the old one):</h3>
22+
<img style=\\"min-width:300px;width:100%;opacity:0\\" src=\\"data:image/png;base64,img-new-base64\\" />
23+
<div style=\\"position:absolute;top:0;left:0;background:#fff\\">
24+
<h3>Old screenshot (hover over to see the new one):</h3>
25+
<img style=\\"min-width:300px;width:100%\\" src=\\"data:image/png;base64,img-old-base64\\" />
26+
</div>
27+
</div>
28+
<div style=\\"background:#fff;border:solid 15px #fff\\">
29+
<h3>Diff between new and old screenshot</h3>
30+
<img style=\\"min-width:300px;width:100%\\" src=\\"data:image/png;base64,img-diff-base64\\" />
31+
</div>
32+
</div>
33+
</div>
34+
</div>"
35+
`;
36+
37+
exports[`generateOverlayTemplate > generates proper template 2`] = `
38+
"<div class=\\"cp-visual-regression-diff-overlay runner\\" style=\\"position:fixed;z-index:10;top:0;bottom:0;left:0;right:0;display:flex;flex-flow:column\\">
39+
<header style=\\"position:static\\">
40+
<nav style=\\"display:flex;width:100%;align-items:center;justify-content:space-between;padding:10px 15px;\\">
41+
<h2>some title - screenshot diff</h2>
42+
<form>
43+
Image was already updated, rerun test to see new comparison
44+
<button type=\\"button\\" data-type=\\"close\\"><i class=\\"fa fa-times\\"></i> Close</button>
45+
<form>
46+
</nav>
47+
</header>
48+
<div style=\\"padding:15px;overflow:auto\\">
49+
<div style=\\"display:flex;justify-content:space-evenly;align-items:flex-start;gap:15px\\">
50+
<div
51+
style=\\"position:relative;background:#fff;border:solid 15px #fff\\"
52+
onmouseover=\\"this.querySelector('div').style.opacity=0,this.querySelector('img').style.opacity=1\\"
53+
onmouseleave=\\"this.querySelector('div').style.opacity=1,this.querySelector('img').style.opacity=0\\"
54+
>
55+
<h3>New screenshot (hover mouse away too see the old one):</h3>
56+
<img style=\\"min-width:300px;width:100%;opacity:0\\" src=\\"data:image/png;base64,img-new-base64\\" />
57+
<div style=\\"position:absolute;top:0;left:0;background:#fff\\">
58+
<h3>Old screenshot (hover over to see the new one):</h3>
59+
<img style=\\"min-width:300px;width:100%\\" src=\\"data:image/png;base64,img-old-base64\\" />
60+
</div>
61+
</div>
62+
<div style=\\"background:#fff;border:solid 15px #fff\\">
63+
<h3>Diff between new and old screenshot</h3>
64+
<img style=\\"min-width:300px;width:100%\\" src=\\"data:image/png;base64,img-diff-base64\\" />
65+
</div>
66+
</div>
67+
</div>
68+
</div>"
69+
`;
60.7 KB
Loading
32.3 KB
Loading

src/__tests__/fixtures/screenshot.png

43.6 KB
Loading

src/__tests__/mocks/cypress.mock.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { vi } from "vitest";
2+
import { promises as fs } from "fs";
3+
4+
export const Cypress = {
5+
Promise,
6+
};
7+
vi.stubGlobal("Cypress", Cypress);
8+
9+
export const cy = {
10+
readFile: vi.fn(fs.readFile),
11+
};
12+
vi.stubGlobal("cy", cy);
13+
14+
export const before = vi.fn();
15+
vi.stubGlobal("before", before);
16+
17+
export const after = vi.fn();
18+
vi.stubGlobal("after", after);

src/afterScreenshot.hook.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { it, expect, describe } from "vitest";
2+
import path from "path";
3+
import { promises as fs, existsSync } from "fs";
4+
import { initAfterScreenshotHook } from "@/afterScreenshot.hook";
5+
import { dir, file, setGracefulCleanup } from "tmp-promise";
6+
import { IMAGE_SNAPSHOT_PREFIX } from "@/constants";
7+
8+
setGracefulCleanup();
9+
10+
describe("initAfterScreenshotHook", () => {
11+
it("move file and remove old directories", async () => {
12+
const { path: screenshotsFolder } = await dir();
13+
const imagesFolder = path.join(screenshotsFolder, IMAGE_SNAPSHOT_PREFIX);
14+
await fs.mkdir(imagesFolder);
15+
const { path: imgPath } = await file();
16+
const projectRoot = path.dirname(imgPath);
17+
18+
await initAfterScreenshotHook({
19+
screenshotsFolder,
20+
projectRoot,
21+
} as Cypress.PluginConfigOptions)({
22+
name: IMAGE_SNAPSHOT_PREFIX + path.sep + "some_name",
23+
path: imgPath,
24+
} as Cypress.ScreenshotDetails);
25+
26+
const expectedNewPath = path.join(projectRoot, "some_name");
27+
expect(existsSync(imagesFolder)).toBe(false);
28+
expect(existsSync(imgPath)).toBe(false);
29+
expect(existsSync(expectedNewPath)).toBe(true);
30+
31+
await fs.unlink(expectedNewPath);
32+
});
33+
});

src/afterScreenshot.hook.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import path from "path";
2+
import fs from "fs";
3+
import moveFile from "move-file";
4+
import { IMAGE_SNAPSHOT_PREFIX } from "@/constants";
5+
6+
type NotFalsy<T> = T extends false | null | undefined ? never : T;
7+
8+
const getConfigVariableOrThrow = <K extends keyof Cypress.PluginConfigOptions>(
9+
config: Cypress.PluginConfigOptions,
10+
name: K
11+
) => {
12+
if (config[name]) {
13+
return config[name] as NotFalsy<Cypress.PluginConfigOptions[K]>;
14+
}
15+
16+
/* c8 ignore start */
17+
throw `[Image snapshot] CypressConfig.${name} cannot be missing or \`false\`!`;
18+
};
19+
/* c8 ignore stop */
20+
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+
);
36+
};
37+
38+
export const initAfterScreenshotHook =
39+
(config: Cypress.PluginConfigOptions) =>
40+
(
41+
details: Cypress.ScreenshotDetails
42+
):
43+
| void
44+
| Cypress.AfterScreenshotReturnObject
45+
| Promise<Cypress.AfterScreenshotReturnObject> => {
46+
/* c8 ignore start */
47+
if (details.name?.indexOf(IMAGE_SNAPSHOT_PREFIX) !== 0) return;
48+
/* c8 ignore stop */
49+
return new Promise((resolve, reject) => {
50+
const screenshotsFolder = getConfigVariableOrThrow(
51+
config,
52+
"screenshotsFolder"
53+
);
54+
55+
const newRelativePath = details.name.substring(
56+
IMAGE_SNAPSHOT_PREFIX.length + path.sep.length
57+
);
58+
const newAbsolutePath = path.normalize(
59+
path.join(config.projectRoot, newRelativePath)
60+
);
61+
62+
void moveFile(details.path, newAbsolutePath)
63+
.then(() =>
64+
removeScreenshotsDirectory(
65+
screenshotsFolder,
66+
() => resolve({ path: newAbsolutePath }),
67+
reject
68+
)
69+
)
70+
.catch(reject);
71+
});
72+
};

src/commands.ts

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,41 @@ const constructCypressError = (log: Cypress.Log, err: Error) => {
3535
return err;
3636
};
3737

38+
export const getConfig = (options: Cypress.MatchImageOptions) => ({
39+
scaleFactor:
40+
Cypress.env("pluginVisualRegressionForceDeviceScaleFactor") === false
41+
? 1
42+
: 1 / window.devicePixelRatio,
43+
updateImages:
44+
options.updateImages ||
45+
(Cypress.env("pluginVisualRegressionUpdateImages") as
46+
| boolean
47+
| undefined) ||
48+
false,
49+
imagesDir:
50+
options.imagesDir ||
51+
(Cypress.env("pluginVisualRegressionImagesDir") as string | undefined) ||
52+
"__image_snapshots__",
53+
maxDiffThreshold:
54+
options.maxDiffThreshold ||
55+
(Cypress.env("pluginVisualRegressionMaxDiffThreshold") as
56+
| number
57+
| undefined) ||
58+
0.01,
59+
diffConfig:
60+
options.diffConfig ||
61+
(Cypress.env("pluginVisualRegressionDiffConfig") as
62+
| Parameters<typeof pixelmatch>[5]
63+
| undefined) ||
64+
{},
65+
screenshotConfig:
66+
options.screenshotConfig ||
67+
(Cypress.env("pluginVisualRegressionScreenshotConfig") as
68+
| Partial<Cypress.ScreenshotDefaultsOptions>
69+
| undefined) ||
70+
{},
71+
});
72+
3873
Cypress.Commands.add(
3974
"matchImage",
4075
{ prevSubject: "optional" },
@@ -45,38 +80,14 @@ Cypress.Commands.add(
4580
nameCacheCounter[title] = -1;
4681
title += ` #${++nameCacheCounter[title]}`;
4782

48-
const scaleFactor =
49-
Cypress.env("pluginVisualRegressionForceDeviceScaleFactor") === false
50-
? 1
51-
: 1 / window.devicePixelRatio;
52-
const updateImages =
53-
options.updateImages ||
54-
(Cypress.env("pluginVisualRegressionUpdateImages") as
55-
| boolean
56-
| undefined) ||
57-
false;
58-
const imagesDir =
59-
options.imagesDir ||
60-
(Cypress.env("pluginVisualRegressionImagesDir") as string | undefined) ||
61-
"__image_snapshots__";
62-
const maxDiffThreshold =
63-
options.maxDiffThreshold ||
64-
(Cypress.env("pluginVisualRegressionMaxDiffThreshold") as
65-
| number
66-
| undefined) ||
67-
0.01;
68-
const diffConfig =
69-
options.diffConfig ||
70-
(Cypress.env("pluginVisualRegressionDiffConfig") as
71-
| Parameters<typeof pixelmatch>[5]
72-
| undefined) ||
73-
{};
74-
const screenshotConfig =
75-
options.screenshotConfig ||
76-
(Cypress.env("pluginVisualRegressionScreenshotConfig") as
77-
| Partial<Cypress.ScreenshotDefaultsOptions>
78-
| undefined) ||
79-
{};
83+
const {
84+
scaleFactor,
85+
updateImages,
86+
imagesDir,
87+
maxDiffThreshold,
88+
diffConfig,
89+
screenshotConfig,
90+
} = getConfig(options);
8091

8192
return cy
8293
.then(() =>

0 commit comments

Comments
 (0)