Skip to content

Commit 261d1bf

Browse files
committed
test: add animated visual smoke coverage
1 parent ea818a9 commit 261d1bf

3 files changed

Lines changed: 58 additions & 0 deletions

File tree

docs/visual-acceptance.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ The baseline covers:
99
- mobile hero, team and contact viewport states;
1010
- real scroll/anchor positions rather than isolated components.
1111

12+
There is also a non-baseline animation smoke test. It runs with reduced motion
13+
disabled and verifies that two captured hero frames differ, while keeping the
14+
approved screenshot baselines static and deterministic.
15+
1216
Update the approved screenshots only when a visual change is intentional:
1317

1418
```bash

playwright.config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default defineConfig({
2121
projects: [
2222
{
2323
name: 'desktop',
24+
testIgnore: /animation\.spec\.ts/,
2425
use: {
2526
...devices['Desktop Chrome'],
2627
viewport: { width: 1440, height: 1100 },
@@ -29,12 +30,26 @@ export default defineConfig({
2930
},
3031
{
3132
name: 'mobile',
33+
testIgnore: /animation\.spec\.ts/,
3234
use: {
3335
...devices['Pixel 5'],
3436
viewport: { width: 390, height: 844 },
3537
deviceScaleFactor: 1,
3638
isMobile: true,
3739
},
3840
},
41+
{
42+
name: 'animation',
43+
testMatch: /animation\.spec\.ts/,
44+
use: {
45+
...devices['Desktop Chrome'],
46+
viewport: { width: 1440, height: 1100 },
47+
deviceScaleFactor: 1,
48+
reducedMotion: 'no-preference',
49+
launchOptions: {
50+
args: ['--ignore-gpu-blocklist', '--use-gl=swiftshader'],
51+
},
52+
},
53+
},
3954
],
4055
});

tests/acceptance/animation.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
function imageDelta(a: Buffer, b: Buffer) {
4+
if (a.length !== b.length) {
5+
return 1;
6+
}
7+
8+
let changed = 0;
9+
for (let i = 0; i < a.length; i += 1) {
10+
if (Math.abs(a[i] - b[i]) > 6) {
11+
changed += 1;
12+
}
13+
}
14+
15+
return changed / a.length;
16+
}
17+
18+
test.describe('animated design smoke', () => {
19+
test('hero motion changes between captured frames', async ({ page }) => {
20+
await page.goto('/', { waitUntil: 'domcontentloaded' });
21+
await expect(page.getByRole('heading', { name: /Software that lasts/i })).toBeVisible();
22+
23+
await page.waitForTimeout(80);
24+
const first = await page.screenshot({
25+
clip: { x: 0, y: 0, width: 1440, height: 760 },
26+
animations: 'allow',
27+
caret: 'hide',
28+
});
29+
30+
await page.waitForTimeout(520);
31+
const second = await page.screenshot({
32+
clip: { x: 0, y: 0, width: 1440, height: 760 },
33+
animations: 'allow',
34+
caret: 'hide',
35+
});
36+
37+
expect(imageDelta(first, second), 'animated hero frame should change over time').toBeGreaterThan(0.001);
38+
});
39+
});

0 commit comments

Comments
 (0)