Skip to main content
visual-regression-testing Screenshot comparison patterns for catching unintended UI changes with Playwright. Use when setting up visual regression tests, configuring screenshot thresholds, handling dynamic content, or integrating visual checks into CI/CD pipelines.
Zur Installation springen Skills Marktplatz Entdecken und erkunden Sie KI-Skills, die von der Community erstellt wurden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Prompt kopierenPrompt-Details anzeigen Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
npx skills add https://github.com/AbhiRKeesara/test-automation-skills --skill visual-regression-testingDer Befehl bleibt in einer Zeile. Scrollen Sie horizontal, um ihn vor dem Kopieren vollständig zu prüfen.
Sie bevorzugen eine lokale Kopie? Laden Sie die Dateien herunter, die SkillsMP derzeit vorliegen.
ZIP herunterladen Herunterladen... Verwandte Berufe SOC
Basierend auf der SOC-Berufsklassifikation
name visual-regression-testing description Screenshot comparison patterns for catching unintended UI changes with Playwright. Use when setting up visual regression tests, configuring screenshot thresholds, handling dynamic content, or integrating visual checks into CI/CD pipelines.
Visual Regression Testing Skill
Best practices for visual regression testing with Playwright to catch unintended UI changes.
Why Visual Regression Testing
Catch UI bugs - Detect unexpected layout shifts, styling changes
Cross-browser consistency - Ensure UI looks correct across browsers
Design system compliance - Verify components match design specs
Regression prevention - Prevent CSS changes from breaking existing pages
Table of Contents
Screenshot Comparisons
Full Page Screenshots
import { test, expect } from '@playwright/test' ;
test ('homepage visual regression' , async ({ page }) => {
await page.goto ('/' );
page. ( );
(page). ( , {
: ,
});
});
await
waitForLoadState
'networkidle'
await
expect
toHaveScreenshot
'homepage.png'
fullPage
true
Component Screenshots test ('product card visual regression' , async ({ page }) => {
await page.goto ('/products' );
const productCard = page.getByTestId ('product-card' ).first ();
await expect (productCard).toHaveScreenshot ('product-card.png' );
});
test ('navigation menu visual regression' , async ({ page }) => {
await page.goto ('/' );
const navbar = page.getByRole ('navigation' );
await expect (navbar).toHaveScreenshot ('navigation.png' );
});
Multiple Viewport Sizes const viewports = [
{ width : 1920 , height : 1080 , name : 'desktop' },
{ width : 1024 , height : 768 , name : 'tablet' },
{ width : 375 , height : 667 , name : 'mobile' },
];
for (const viewport of viewports) {
test (`homepage at ${viewport.name} ` , async ({ page }) => {
await page.setViewportSize ({ width : viewport.width , height : viewport.height });
await page.goto ('/' );
await page.waitForLoadState ('networkidle' );
await expect (page).toHaveScreenshot (`homepage-${viewport.name} .png` , {
fullPage : true ,
});
});
}
Configuration
Playwright Config for Visual Testing
import { defineConfig, devices } from '@playwright/test' ;
export default defineConfig ({
testDir : './tests' ,
expect : {
toHaveScreenshot : {
maxDiffPixels : 100 ,
maxDiffPixelRatio : 0.01 ,
threshold : 0.2 ,
animations : 'disabled' ,
caret : 'hide' ,
scale : 'css' ,
},
},
updateSnapshots : process.env .UPDATE_SNAPSHOTS ? 'all' : 'missing' ,
projects : [
{
name : 'chromium' ,
use : {
...devices['Desktop Chrome' ],
launchOptions : {
args : ['--font-render-hinting=none' ],
},
},
},
{
name : 'firefox' ,
use : { ...devices['Desktop Firefox' ] },
},
{
name : 'webkit' ,
use : { ...devices['Desktop Safari' ] },
},
],
});
Screenshot Options test ('configurable screenshot options' , async ({ page }) => {
await page.goto ('/products' );
await expect (page).toHaveScreenshot ('products-page.png' , {
fullPage : true ,
maxDiffPixels : 50 ,
maxDiffPixelRatio : 0.005 ,
threshold : 0.1 ,
animations : 'disabled' ,
caret : 'hide' ,
mask : [
page.getByTestId ('timestamp' ),
page.getByTestId ('user-avatar' ),
],
maskColor : '#FF00FF' ,
omitBackground : false ,
scale : 'css' ,
timeout : 10000 ,
});
});
Testing Strategies
Component Library Testing test.describe ('Button Component Visual Tests' , () => {
test ('primary button states' , async ({ page }) => {
await page.goto ('/storybook/buttons' );
const primaryButton = page.getByRole ('button' , { name : 'Primary' });
await expect (primaryButton).toHaveScreenshot ('button-primary-default.png' );
await primaryButton.hover ();
await expect (primaryButton).toHaveScreenshot ('button-primary-hover.png' );
await primaryButton.focus ();
await expect (primaryButton).toHaveScreenshot ('button-primary-focus.png' );
const disabledButton = page.getByRole ('button' , { name : 'Disabled' });
await expect (disabledButton).toHaveScreenshot ('button-primary-disabled.png' );
});
});
Form Validation States test.describe ('Form Visual States' , () => {
test ('input field states' , async ({ page }) => {
await page.goto ('/login' );
const emailInput = page.getByLabel ('Email' );
await expect (emailInput).toHaveScreenshot ('input-empty.png' );
await emailInput.fill ('user@example.com' );
await expect (emailInput).toHaveScreenshot ('input-filled.png' );
await emailInput.fill ('invalid-email' );
await page.getByRole ('button' , { name : 'Submit' }).click ();
await expect (emailInput).toHaveScreenshot ('input-error.png' );
});
});
Dark Mode Testing test.describe ('Dark Mode Visual Tests' , () => {
test ('homepage in dark mode' , async ({ page }) => {
await page.emulateMedia ({ colorScheme : 'dark' });
await page.goto ('/' );
await expect (page).toHaveScreenshot ('homepage-dark.png' , {
fullPage : true ,
});
});
test ('homepage in light mode' , async ({ page }) => {
await page.emulateMedia ({ colorScheme : 'light' });
await page.goto ('/' );
await expect (page).toHaveScreenshot ('homepage-light.png' , {
fullPage : true ,
});
});
});
Responsive Design Testing test.describe ('Responsive Design' , () => {
const breakpoints = {
mobile : { width : 375 , height : 667 },
tablet : { width : 768 , height : 1024 },
desktop : { width : 1440 , height : 900 },
};
for (const [name, size] of Object .entries (breakpoints)) {
test (`navigation at ${name} breakpoint` , async ({ page }) => {
await page.setViewportSize (size);
await page.goto ('/' );
const nav = page.getByRole ('navigation' );
await expect (nav).toHaveScreenshot (`nav-${name} .png` );
});
}
});
Handling Dynamic Content
Masking Dynamic Elements test ('page with dynamic content' , async ({ page }) => {
await page.goto ('/dashboard' );
await expect (page).toHaveScreenshot ('dashboard.png' , {
mask : [
page.locator ('[data-testid="timestamp"]' ),
page.locator ('[data-testid="user-name"]' ),
page.locator ('img[src*="avatar"]' ),
page.locator ('[data-testid="live-chart"]' ),
],
});
});
Waiting for Stability test ('page with lazy loaded content' , async ({ page }) => {
await page.goto ('/products' );
await page.waitForFunction (() => {
const images = document .querySelectorAll ('img' );
return Array .from (images).every (img => img.complete );
});
await page.waitForTimeout (500 );
await expect (page).toHaveScreenshot ('products-loaded.png' , {
fullPage : true ,
});
});
Freezing Animations test ('page with animations' , async ({ page }) => {
await page.goto ('/' );
await page.addStyleTag ({
content : `
*, *::before, *::after {
animation-duration: 0s !important;
animation-delay: 0s !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
}
` ,
});
await expect (page).toHaveScreenshot ('homepage-no-animations.png' );
});
Handling Date/Time test ('page with dates' , async ({ page }) => {
await page.addInitScript (() => {
const fixedDate = new Date ('2026-01-15T10:00:00Z' );
Date .now = () => fixedDate.getTime ();
});
await page.goto ('/dashboard' );
await expect (page).toHaveScreenshot ('dashboard-fixed-date.png' );
});
CI/CD Integration
GitHub Actions Workflow
name: Visual Regression Tests
on:
push:
branches: [main ]
pull_request:
branches: [main ]
jobs:
visual-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run visual tests
run: npx playwright test --project=chromium
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: visual-test-results
path: |
test-results/
playwright-report/
Updating Snapshots
npx playwright test --update-snapshots
npx playwright test homepage.spec.ts --update-snapshots
UPDATE_SNAPSHOTS=1 npx playwright test
Snapshot Storage
export default defineConfig ({
snapshotDir : './snapshots' ,
snapshotPathTemplate : '{snapshotDir}/{testFilePath}/{arg}{ext}' ,
});
Best Practices
1. Organize Screenshots by Feature snapshots/
├── auth/
│ ├── login-page.png
│ ├── register-page.png
│ └── forgot-password.png
├── products/
│ ├── catalog-desktop.png
│ ├── catalog-mobile.png
│ └── product-details.png
└── checkout/
├── cart.png
├── shipping.png
└── payment.png
2. Use Descriptive Screenshot Names
await expect (page).toHaveScreenshot ('checkout-cart-with-items.png' );
await expect (page).toHaveScreenshot ('checkout-cart-empty-state.png' );
await expect (page).toHaveScreenshot ('product-card-out-of-stock.png' );
await expect (page).toHaveScreenshot ('screenshot1.png' );
await expect (page).toHaveScreenshot ('page.png' );
3. Test Meaningful States test.describe ('Product Card Visual States' , () => {
test ('in stock state' , async ({ page }) => {
await page.goto ('/products/in-stock-item' );
await expect (page.getByTestId ('product-card' )).toHaveScreenshot ('product-in-stock.png' );
});
test ('out of stock state' , async ({ page }) => {
await page.goto ('/products/out-of-stock-item' );
await expect (page.getByTestId ('product-card' )).toHaveScreenshot ('product-out-of-stock.png' );
});
test ('on sale state' , async ({ page }) => {
await page.goto ('/products/sale-item' );
await expect (page.getByTestId ('product-card' )).toHaveScreenshot ('product-on-sale.png' );
});
});
4. Keep Snapshots in Version Control # .gitignore
# Ignore test results, but keep snapshots
test-results/
playwright-report/
# Keep snapshots in version control
# !snapshots/
5. Review Snapshot Changes Carefully
test ('checkout summary' , async ({ page }) => {
await page.goto ('/checkout' );
await expect (page).toHaveScreenshot ('checkout-summary.png' , {
fullPage : true ,
});
});
Quick Reference
Screenshot Methods
await expect (page).toHaveScreenshot ('name.png' , { fullPage : true });
await expect (page).toHaveScreenshot ('name.png' );
await expect (locator).toHaveScreenshot ('name.png' );
await expect (page).toHaveScreenshot ('name.png' , {
maxDiffPixels : 100 ,
threshold : 0.2 ,
animations : 'disabled' ,
mask : [locator1, locator2],
});
Update Commands
npx playwright test --update-snapshots
npx playwright test file.spec.ts --update-snapshots
npx playwright test --ui
Related Resources Mehr aus diesem Repository Accessibility (a11y) testing patterns with Playwright and axe-core. Use when adding WCAG 2.1 compliance checks, keyboard navigation testing, screen reader compatibility, or color contrast validation to your test suite.
Storage state reuse, 2FA/TOTP testing, multi-role auth, session management, OAuth flows, and secure credential handling in Playwright
GitHub Actions setup, parallel execution, sharding, test filtering, artifact management, and pipeline optimization for Playwright tests