Visual Regression Testing with Screenshot API
Visual regression testing is a powerful technique that helps catch unintended visual changes in your user interface. By comparing screenshots of your application before and after code changes, you can identify visual bugs that traditional functional tests might miss. This guide will show you how to implement visual regression testing using our Screenshot API.
Why Visual Regression Testing Matters
Traditional unit and integration tests verify that your code functions correctly, but they don't catch visual issues like:
- Misaligned elements
- Overlapping text
- Incorrect colors or styling
- Missing images or icons
- Responsive design breakages
- Cross-browser rendering differences
Setting Up Your Testing Pipeline
1. Capture Baseline Screenshots
First, you need to establish baseline screenshots of your application in a known good state:
01const fs = require('fs').promises;02const path = require('path');03const fetch = require('node-fetch');0405async function captureBaselines(pages) {06 const baselineDir = path.join(__dirname, 'baselines');07 await fs.mkdir(baselineDir, { recursive: true });0809 for (const page of pages) {10 console.log(`Capturing baseline for ${page.name}...`);1112 const response = await fetch('https://api.screenshotapi.com/capture', {13 method: 'POST',14 headers: {15 'Content-Type': 'application/json',16 'Authorization': `Bearer ${process.env.SCREENSHOT_API_KEY}`17 },18 body: JSON.stringify({19 url: page.url,20 width: 1280,21 height: 800,22 fullPage: true,23 format: 'png'24 })25 });2627 const data = await response.json();2829 // Download the screenshot30 const imageResponse = await fetch(data.screenshotUrl);31 const imageBuffer = await imageResponse.buffer();3233 // Save as baseline34 await fs.writeFile(35 path.join(baselineDir, `${page.name}.png`),36 imageBuffer37 );38 }39}4041// Define pages to test42const pages = [43 { name: 'homepage', url: 'https://your-app.com/' },44 { name: 'login', url: 'https://your-app.com/login' },45 { name: 'dashboard', url: 'https://your-app.com/dashboard' }46];4748captureBaselines(pages);
2. Implement Comparison Logic
Next, create a function to compare new screenshots against the baselines:
01const { PNG } = require('pngjs');02const pixelmatch = require('pixelmatch');0304async function compareWithBaseline(pageName, currentScreenshotBuffer) {05 const baselineDir = path.join(__dirname, 'baselines');06 const diffDir = path.join(__dirname, 'diffs');0708 await fs.mkdir(diffDir, { recursive: true });0910 // Read baseline image11 const baselineBuffer = await fs.readFile(12 path.join(baselineDir, `${pageName}.png`)13 );1415 // Parse PNG images16 const baseline = PNG.sync.read(baselineBuffer);17 const current = PNG.sync.read(currentScreenshotBuffer);1819 // Create output image20 const { width, height } = baseline;21 const diff = new PNG({ width, height });2223 // Compare pixels24 const numDiffPixels = pixelmatch(25 baseline.data,26 current.data,27 diff.data,28 width,29 height,30 { threshold: 0.1 }31 );3233 // Calculate difference percentage34 const totalPixels = width * height;35 const percentageDifference = (numDiffPixels / totalPixels) * 100;3637 // Save diff image if there are differences38 if (numDiffPixels > 0) {39 await fs.writeFile(40 path.join(diffDir, `${pageName}-diff.png`),41 PNG.sync.write(diff)42 );43 }4445 return {46 hasDifferences: numDiffPixels > 0,47 diffPixels: numDiffPixels,48 percentageDifference,49 diffPath: numDiffPixels > 0 ? path.join(diffDir, `${pageName}-diff.png`) : null50 };51}
3. Integrate with CI/CD Pipeline
Now, integrate the visual testing into your CI/CD pipeline:
01const fetch = require('node-fetch');0203async function runVisualTests(pages) {04 let failedTests = 0;0506 for (const page of pages) {07 console.log(`Testing ${page.name}...`);0809 // Capture current state10 const response = await fetch('https://api.screenshotapi.com/capture', {11 method: 'POST',12 headers: {13 'Content-Type': 'application/json',14 'Authorization': `Bearer ${process.env.SCREENSHOT_API_KEY}`15 },16 body: JSON.stringify({17 url: page.url,18 width: 1280,19 height: 800,20 fullPage: true,21 format: 'png'22 })23 });2425 const data = await response.json();2627 // Download the screenshot28 const imageResponse = await fetch(data.screenshotUrl);29 const imageBuffer = await imageResponse.buffer();3031 // Compare with baseline32 const result = await compareWithBaseline(page.name, imageBuffer);3334 if (result.hasDifferences) {35 console.error(`❌ ${page.name} has visual differences!`);36 console.error(` ${result.percentageDifference.toFixed(2)}% of pixels differ`);37 console.error(` Diff image saved to: ${result.diffPath}`);38 failedTests++;39 } else {40 console.log(`✅ ${page.name} looks identical to baseline`);41 }42 }4344 if (failedTests > 0) {45 console.error(`\n${failedTests} tests failed with visual differences`);46 process.exit(1); // Fail the CI build47 } else {48 console.log('\nAll visual tests passed!');49 }50}
GitHub Actions Integration
Here's how to integrate this into a GitHub Actions workflow:
01name: Visual Regression Tests0203on:04 pull_request:05 branches: [ main ]0607jobs:08 visual-test:09 runs-on: ubuntu-latest1011 steps:12 - uses: actions/checkout@v21314 - name: Setup Node.js15 uses: actions/setup-node@v216 with:17 node-version: '16'1819 - name: Install dependencies20 run: npm install2122 - name: Run visual regression tests23 run: node visual-regression-tests.js24 env:25 SCREENSHOT_API_KEY: ${{ secrets.SCREENSHOT_API_KEY }}2627 - name: Upload diff images if tests fail28 if: failure()29 uses: actions/upload-artifact@v230 with:31 name: visual-diffs32 path: diffs/
Handling Dynamic Content
Dynamic content can cause false positives in visual regression tests. Here are strategies to handle it:
1. Element Masking
Hide dynamic elements before capturing:
01// Add executeBeforeScreenshot to hide dynamic elements02body: JSON.stringify({03 url: page.url,04 executeBeforeScreenshot: `05 // Hide date/time elements06 document.querySelectorAll('.timestamp, .date').forEach(el => {07 el.style.visibility = 'hidden';08 });0910 // Hide user-specific content11 document.querySelectorAll('.user-avatar, .username').forEach(el => {12 el.style.visibility = 'hidden';13 });14 `15})
2. Region-Based Comparison
Only compare specific regions of the page:
01// Modify the comparison function to only compare specific regions02const regions = [03 { x: 0, y: 0, width: 1280, height: 200 }, // Header04 { x: 200, y: 300, width: 800, height: 400 } // Main content05];0607// For each region, extract and compare that portion of the image08for (const region of regions) {09 // Extract region from both images and compare10 // ...11}
Best Practices
- Run tests in a consistent environment: Use containerization to ensure consistent rendering
- Update baselines intentionally: Don't automatically update baselines; review changes first
- Set appropriate thresholds: Allow for minor pixel differences (0.1-1%) to avoid false positives
- Test responsive breakpoints: Capture screenshots at different viewport sizes
- Include cross-browser testing: Test in Chrome, Firefox, Safari, and Edge
By implementing visual regression testing with our Screenshot API, you can catch visual bugs early in your development process, ensuring a consistent and polished user experience.
Ready to Get Started?
Get your API key now and start capturing screenshots in minutes.