Visual regression testing detects unintended UI changes by comparing screenshots against baseline images. Brain-Storm uses Playwright for visual testing.
npm install -D @playwright/test
npx playwright installVisual tests are configured in playwright-visual.config.ts:
export default defineConfig({
testDir: './e2e',
testMatch: '**/*visual*.spec.ts',
use: {
baseURL: 'http://localhost:3001',
screenshot: 'only-on-failure',
},
});import { test, expect } from '@playwright/test';
test('homepage should match baseline', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});Mask elements that change between runs:
await expect(page).toHaveScreenshot('page.png', {
mask: [
page.locator('[data-testid="timestamp"]'),
page.locator('[data-testid="user-avatar"]'),
],
});await expect(page).toHaveScreenshot('full-page.png', {
fullPage: true,
});const button = page.locator('[data-testid="submit-button"]');
await expect(button).toHaveScreenshot('button.png');When intentional UI changes are made, update baselines:
npm run test:visual -- --update-snapshotsnpm run test:visualnpm run test:visual -- visual-regression.spec.tsVisual regression tests run automatically on:
- Pull requests affecting frontend code
- Pushes to main branch
Results are commented on PRs with diff artifacts.
Failed tests generate diff images in test-results/:
test-results/
├── visual-regression-homepage-1-expected.png
├── visual-regression-homepage-1-actual.png
└── visual-regression-homepage-1-diff.png
Enable trace recording:
npm run test:visual -- --trace onView traces:
npx playwright show-trace trace.zip- Mask dynamic content - Always mask timestamps, IDs, avatars
- Test key pages - Focus on critical user journeys
- Use data-testid - Add
data-testidattributes for reliable selectors - Review diffs carefully - Verify changes are intentional
- Keep baselines updated - Update baselines with design changes
- Test responsive - Test at multiple viewport sizes
Test multiple viewports:
test.describe('Responsive Design', () => {
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1920, height: 1080 },
];
viewports.forEach(({ name, width, height }) => {
test(`should match baseline on ${name}`, async ({ page }) => {
await page.setViewportSize({ width, height });
await page.goto('/');
await expect(page).toHaveScreenshot(`homepage-${name}.png`);
});
});
});- Increase wait times:
await page.waitForLoadState('networkidle') - Mask animations: Use
maskoption - Wait for elements:
await page.waitForSelector('[data-testid="content"]')
- Check OS differences (screenshots vary by OS)
- Verify font rendering
- Check for timing issues