Skip to content

Commit 2bf00f2

Browse files
authored
test: add visual tests (#1105)
1 parent 15dfc67 commit 2bf00f2

13 files changed

+257
-33
lines changed

.github/workflows/main.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,27 @@ jobs:
9595
export NODE_OPTIONS="--max-old-space-size=7680"
9696
pnpm test:coverage
9797
98+
- name: Commit changed files
99+
if: ${{ github.ref_name != 'master' && always() }}
100+
run: |
101+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
102+
git config --local user.name "github-actions[bot]"
103+
git add .
104+
git commit -m "chore: upload changed files during CI" || true
105+
106+
- name: Run visual tests
107+
if: ${{ github.ref_name != 'master' && always() }}
108+
run: |
109+
cd packages/playground
110+
pnpm test:vitest:visual
111+
112+
- name: Push changed files
113+
if: ${{ github.ref_name != 'master' && always() }}
114+
run: |
115+
git add .
116+
git commit -m "chore: upload changed files during CI" || true
117+
git push || true
118+
98119
- name: Upload coverage information to codecov
99120
uses: codecov/codecov-action@v3
100121
with:

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ packages/*/public/share
66
nohup.out
77
.venv
88
*.tsbuildinfo
9-
__img_snapshots__
109
.turbo
1110
temp
1211

packages/playground/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@
1212
"preview": "vite preview --port 3001",
1313
"build": "vite build",
1414
"test:vitest": "vitest --no-threads",
15-
"test:vitest:coverage": "vitest run --no-threads --coverage"
15+
"test:vitest:coverage": "vitest run --no-threads --coverage",
16+
"test:vitest:visual": "vitest run --no-threads test/5.visual.spec.ts -u"
1617
},
1718
"devDependencies": {
1819
"@rino.app/editor": "workspace:*",
1920
"@types/react": "^18.0.15",
2021
"@types/react-dom": "^18.0.6",
2122
"@vitejs/plugin-react": "^2.0.0",
2223
"c8": "^7.12.0",
24+
"jest-image-snapshot": "^5.1.0",
2325
"jest-remirror": "^2.0.0-beta.12",
2426
"jsdom": "^20.0.0",
2527
"lodash-es": "^4.17.21",

packages/playground/test/5.styles.spec.ts renamed to packages/playground/test/5.visual.spec.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import "./setup-image-snapshot"
2+
3+
import { describe, expect, test } from "vitest"
4+
15
import { switchToSourceCodeMode } from "./actions"
2-
import { setupEditor, writeSnapshotImage } from "./utils"
6+
import { setupEditor } from "./utils"
37

48
const content = `# first heading in the document (zero margin-top)
59
@@ -10,11 +14,11 @@ A long long long long long long long long long long long long long long long lon
1014
- list
1115
## heading in a list item (normal margin-top and margin-bottom)
1216
13-
> ### first heading in a quotablock (zero margin-top)
17+
> ### first heading in a blockquote (zero margin-top)
1418
>
15-
> ### second heading in a quotablock (normal margin-top and margin-bottom)
19+
> ### second heading in a blockquote (normal margin-top and margin-bottom)
1620
>
17-
> ### last heading in a quotablock (zero margin-bottom)
21+
> ### last heading in a blockquote (zero margin-bottom)
1822
1923
## nested list
2024
@@ -38,23 +42,26 @@ A long long long long long long long long long long long long long long long lon
3842
3943
`
4044

41-
describe("macOS", () => {
42-
test("wysiwyg mode", async () => {
43-
if (process.platform !== "darwin") {
44-
return
45-
}
45+
async function expectMatchSnapshot() {
46+
const screenshot = await page.screenshot({ type: "png", fullPage: true })
47+
expect(screenshot).toMatchImageSnapshot({
48+
customDiffConfig: {
49+
threshold: 0.05,
50+
},
51+
// How many differing pixels is allowed.
52+
failureThreshold: 15,
53+
})
54+
}
4655

56+
describe(`${process.platform}`, () => {
57+
test("wysiwyg mode", async () => {
4758
await setupEditor(content)
4859
await page.focus(".blur-helper") // hide the cursor
49-
await writeSnapshotImage("macOS.wysiwyg.png")
60+
await expectMatchSnapshot()
5061
})
5162

5263
test("source code mode", async () => {
53-
if (process.platform !== "darwin") {
54-
return
55-
}
56-
5764
await switchToSourceCodeMode()
58-
await writeSnapshotImage("macOS.sourcecode.png")
65+
await expectMatchSnapshot()
5966
})
6067
})
Loading
Loading
Loading
Loading
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* eslint-disable @typescript-eslint/no-namespace */
2+
3+
// @ts-expect-error: jest-image-snapshot doesn't have built-in types
4+
import { toMatchImageSnapshot } from "jest-image-snapshot"
5+
6+
expect.extend({ toMatchImageSnapshot })
7+
8+
declare global {
9+
namespace jest {
10+
interface Matchers<R> {
11+
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): R
12+
}
13+
}
14+
}
15+
16+
interface MatchImageSnapshotOptions {
17+
/**
18+
* If set to true, the build will not fail when the screenshots to compare have different sizes.
19+
* @default false
20+
*/
21+
allowSizeMismatch?: boolean | undefined
22+
/**
23+
* Custom config passed to 'pixelmatch' or 'ssim'
24+
*/
25+
customDiffConfig?: {
26+
/**
27+
* Per pixel sensitivity threshold, ranges from 0 to 1.
28+
*/
29+
threshold: number
30+
}
31+
/**
32+
* The method by which images are compared.
33+
* `pixelmatch` does a pixel by pixel comparison, whereas `ssim` does a structural similarity comparison.
34+
* @default 'pixelmatch'
35+
*/
36+
comparisonMethod?: "pixelmatch" | "ssim" | undefined
37+
/**
38+
* Custom snapshots directory.
39+
* Absolute path of a directory to keep the snapshot in.
40+
*/
41+
customSnapshotsDir?: string | undefined
42+
/**
43+
* A custom absolute path of a directory to keep this diff in
44+
*/
45+
customDiffDir?: string | undefined
46+
/**
47+
* Store the received images separately from the composed diff images on failure.
48+
* This can be useful when updating baseline images from CI.
49+
* @default false
50+
*/
51+
storeReceivedOnFailure?: boolean | undefined
52+
/**
53+
* A custom absolute path of a directory to keep this received image in.
54+
*/
55+
customReceivedDir?: string | undefined
56+
/**
57+
* A custom name to give this snapshot. If not provided, one is computed automatically. When a function is provided
58+
* it is called with an object containing testPath, currentTestName, counter and defaultIdentifier as its first
59+
* argument. The function must return an identifier to use for the snapshot.
60+
*/
61+
customSnapshotIdentifier?:
62+
| ((parameters: { testPath: string; currentTestName: string; counter: number; defaultIdentifier: string }) => string)
63+
| string
64+
| undefined
65+
/**
66+
* Changes diff image layout direction.
67+
* @default 'horizontal'
68+
*/
69+
diffDirection?: "horizontal" | "vertical" | undefined
70+
/**
71+
* Will output base64 string of a diff image to console in case of failed tests (in addition to creating a diff image).
72+
* This string can be copy-pasted to a browser address string to preview the diff for a failed test.
73+
* @default false
74+
*/
75+
dumpDiffToConsole?: boolean | undefined
76+
/**
77+
* Will output the image to the terminal using iTerm's Inline Images Protocol.
78+
* If the term is not compatible, it does the same thing as `dumpDiffToConsole`.
79+
* @default false
80+
*/
81+
dumpInlineDiffToConsole?: boolean | undefined
82+
/**
83+
* Removes coloring from the console output, useful if storing the results to a file.
84+
* @default false.
85+
*/
86+
noColors?: boolean | undefined
87+
/**
88+
* Sets the threshold that would trigger a test failure based on the failureThresholdType selected. This is different
89+
* to the customDiffConfig.threshold above - the customDiffConfig.threshold is the per pixel failure threshold, whereas
90+
* this is the failure threshold for the entire comparison.
91+
* @default 0.
92+
*/
93+
failureThreshold?: number | undefined
94+
/**
95+
* Sets the type of threshold that would trigger a failure.
96+
* @default 'pixel'.
97+
*/
98+
failureThresholdType?: "pixel" | "percent" | undefined
99+
/**
100+
* Updates a snapshot even if it passed the threshold against the existing one.
101+
* @default false.
102+
*/
103+
updatePassedSnapshot?: boolean | undefined
104+
/**
105+
* Applies Gaussian Blur on compared images, accepts radius in pixels as value. Useful when you have noise after
106+
* scaling images per different resolutions on your target website, usually setting its value to 1-2 should be
107+
* enough to solve that problem.
108+
* @default 0.
109+
*/
110+
blur?: number | undefined
111+
/**
112+
* Runs the diff in process without spawning a child process.
113+
* @default false.
114+
*/
115+
runInProcess?: boolean | undefined
116+
}

packages/playground/test/setup-playwright-coverage.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import { collectJSCoverage, startJSCoverage } from "./coverage"
55
async function setupBrowser() {
66
const Playwright = await import("playwright-chromium")
77
const browser = await Playwright.chromium.launch({
8-
headless: !!process.env.CI,
8+
// Default to use headless mode in the CI piplines or GitHub Codespaces
9+
headless: !!(process.env.CI || process.env.CODESPACES),
910
executablePath: process.env.PLAYWRIGHT_CHROME_EXECUTABLE_PATH,
1011
})
11-
const page = await browser.newPage()
12+
const page = await browser.newPage({ viewport: { width: 800, height: 600 } })
1213

1314
globalThis.browser = browser
1415
globalThis.page = page

0 commit comments

Comments
 (0)