Detecting Visual Regressions
This skill performs semantic visual comparison — understanding what UI elements mean and how they relate to each other — instead of brittle pixel-by-pixel diffing. It catches real regressions while maintaining a false positive rate below 2%.
When to Use
- After CSS/layout changes to verify nothing else broke
- When comparing current UI against Figma design specs
- For design system audits (are components consistent?)
- As part of CI/CD visual regression gates
- When migrating UI frameworks or component libraries
Comparison Workflow
Step 1 — Capture Screenshots
Use the capture script to take consistent, comparable screenshots:
python skills/qa-detecting-visual-regressions/scripts/capture.py \
--url https://staging.example.com \
--pages /,/login,/dashboard,/checkout \
--viewport desktop \
--output captures/current/For baselines, either:
- Capture from the known-good version (production or last release)
- Use Figma exports as the reference
Step 2 — Compare
Run semantic comparison between baseline and current captures:
python skills/qa-detecting-visual-regressions/scripts/compare.py \
--baseline captures/baseline/ \
--current captures/current/ \
--output visual-results/ \
--threshold 0.85The comparison analyzes screenshots using Claude Vision (or equivalent VLM) to perform a semantic diff rather than pixel diff.
Step 3 — Review and Classify
The comparison produces a structured report. Each difference is pre-classified, but human review is recommended for borderline cases.
Semantic Diff Approach
Instead of overlaying pixels and counting differences, semantic comparison:
- Identifies elements in both screenshots (buttons, headings, images, forms)
- Maps correspondence between baseline and current (this button = that button)
- Compares properties for each matched pair:
- Position (relative to parent and siblings) - Size (width, height, aspect ratio) - Text content - Color and contrast - Visibility and opacity
- Detects additions and removals (elements in one but not the other)
- Evaluates layout structure (grid alignment, spacing consistency)
Classification Rules
REGRESSION (severity: high)
- Element present in baseline is missing in current
- Element has moved to a completely different section
- Text is truncated or overflowing its container
- Interactive element is obscured by another element
- Layout has collapsed (elements stacking when they shouldn't)
REGRESSION (severity: medium)
- Spacing between elements differs by >8px from baseline
- Font size changed without corresponding design update
- Color changed to something outside the design palette
- Border or shadow properties changed noticeably
COSMETIC (severity: info — not a bug)
- Sub-pixel text rendering differences (<1px shifts)
- Anti-aliasing variations between browsers
- Slightly different scroll positions
- Font hinting differences
DYNAMIC (ignore — expected variation)
- Timestamps, dates, relative time ("3 min ago")
- User-specific content (avatars, notification counts)
- A/B test variants
- Ad slots and third-party embeds
- Animated elements at different frames
Dynamic Region Masking
To reduce false positives from expected dynamic content, define mask regions:
{
"masks": [
{
"page": "/dashboard",
"regions": [
{"selector": ".notification-count", "reason": "dynamic content"},
{"selector": ".user-avatar", "reason": "user-specific"},
{"selector": ".ad-slot", "reason": "third-party"}
]
}
]
}Pass masks to the comparison script with --masks masks.json.
Figma Design Reference
When comparing against Figma:
- Export Figma frames as PNG at 2x resolution
- Map each frame to its application URL:
{
"mappings": [
{"figma_frame": "Homepage - Desktop", "file": "homepage-desktop.png", "url": "/"},
{"figma_frame": "Checkout - Step 1", "file": "checkout-step1.png", "url": "/checkout"}
]
}- Run comparison with
--reference-type figma - Differences represent implementation drift from the design
Color Comparison
Use CIEDE2000 (ΔE2000) for perceptually accurate color comparison:
- ΔE < 1.0: Imperceptible — ignore
- ΔE 1.0–3.0: Slightly noticeable — log as cosmetic
- ΔE 3.0–5.0: Noticeable — investigate
- ΔE > 5.0: Obvious difference — flag as regression
Output Format
visual-results/
├── comparisons/
│ ├── homepage/
│ │ ├── baseline.png
│ │ ├── current.png
│ │ ├── diff-annotated.png
│ │ └── analysis.json
│ └── checkout/
│ └── ...
├── regressions.json ← confirmed regressions only
├── all-differences.json ← everything, classified
└── visual-summary.mdEach regression in regressions.json:
{
"id": "vr-001",
"page": "/checkout",
"severity": "high",
"category": "layout",
"description": "Submit button overlaps price total",
"baseline": "comparisons/checkout/baseline.png",
"current": "comparisons/checkout/current.png",
"diff": "comparisons/checkout/diff-annotated.png",
"affected_elements": ["submit-button", "price-total"],
"confidence": 0.95
}