Skip to content

Commit 85565a8

Browse files
committed
Add visual regression tests
1 parent 9eba156 commit 85565a8

12 files changed

Lines changed: 153 additions & 1 deletion

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,21 @@ jobs:
3030
- run: npm test
3131
- run: npm run examples
3232
- run: npm pack --dry-run
33+
34+
visual:
35+
runs-on: macos-latest
36+
37+
steps:
38+
- uses: actions/checkout@v4
39+
- uses: actions/setup-node@v4
40+
with:
41+
node-version: 24
42+
cache: npm
43+
- run: npm install
44+
- run: npx playwright install chromium
45+
- run: npm run test:visual
46+
- uses: actions/upload-artifact@v4
47+
if: failure()
48+
with:
49+
name: visual-regression-results
50+
path: test-results/visual

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/node_modules
44
/.vscode
55
/dist
6+
/test-results
67

78

89
# Sample Files for Testing

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,17 @@ The package targets Node.js 20, 22, and 24.
7878
npm audit --audit-level=high
7979
npm run build
8080
npm test
81+
npm run test:visual
8182
npm pack --dry-run
8283
```
8384

84-
The example integration test compiles real auth and marketing templates with production-shaped data. A later visual regression layer can render those same baseline emails in a browser and compare screenshots when layout changes need full UI coverage.
85+
The example integration test compiles real auth and marketing templates with production-shaped data. Visual regression tests render the baseline emails in headless Chromium and compare screenshots against approved snapshots.
86+
87+
When an intentional layout change updates the screenshots:
88+
89+
```sh
90+
npm run test:visual -- --update-snapshots
91+
```
8592

8693
## Installation
8794

package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"prepublishOnly": "npm test && npm run build",
2929
"prepack": "npm run build",
3030
"test": "vitest run",
31+
"test:visual": "playwright test",
3132
"test:watch": "vitest"
3233
},
3334
"bin": {
@@ -39,6 +40,7 @@
3940
"juice": "^12.1.0"
4041
},
4142
"devDependencies": {
43+
"@playwright/test": "^1.60.0",
4244
"@types/node": "^20.14.15",
4345
"typescript": "^5.5.4",
4446
"vitest": "^4.1.8"

playwright.config.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './test/visual',
5+
testMatch: '**/*.visual.ts',
6+
outputDir: './test-results/visual',
7+
fullyParallel: true,
8+
forbidOnly: Boolean(process.env.CI),
9+
retries: process.env.CI ? 1 : 0,
10+
reporter: process.env.CI ? [['github'], ['list']] : 'list',
11+
expect: {
12+
toHaveScreenshot: {
13+
maxDiffPixelRatio: 0.01,
14+
threshold: 0.2
15+
}
16+
},
17+
use: {
18+
...devices['Desktop Chrome'],
19+
baseURL: 'http://127.0.0.1:4173',
20+
viewport: {
21+
width: 900,
22+
height: 1200
23+
}
24+
},
25+
webServer: {
26+
command: 'npm run examples:serve',
27+
url: 'http://127.0.0.1:4173',
28+
reuseExistingServer: !process.env.CI,
29+
timeout: 120_000
30+
},
31+
projects: [
32+
{
33+
name: 'chromium',
34+
use: {
35+
browserName: 'chromium'
36+
}
37+
}
38+
]
39+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
const examples = [
4+
'auth-welcome-verify.html',
5+
'marketing-product-launch.html',
6+
'marketing-feature-roundup.html',
7+
'marketing-usage-digest.html',
8+
'marketing-upgrade-offer.html'
9+
];
10+
11+
test.describe('rendered email examples', () => {
12+
for (const example of examples) {
13+
test(`${example} matches the approved baseline`, async ({ page }) => {
14+
await page.goto(`/${example}`);
15+
await expect(page.locator('body')).toBeVisible();
16+
await expect(page).toHaveScreenshot(`${example}.png`, {
17+
fullPage: true
18+
});
19+
});
20+
}
21+
});
48.5 KB
Loading
58.3 KB
Loading
39.5 KB
Loading

0 commit comments

Comments
 (0)