Visual regression API

Visual Regression API (url1 vs url2)

Screenshots two URLs (desktop + mobile), waits for the page to fully settle (network idle, fonts, images, brief LCP), generates a pixel diff, and builds a shareable HTML report.

Endpoint

GET /v1/visual/

Query Parameters

Name Type Required Default Description
url1 string (URL) Yes First page to capture (e.g., production).
url2 string (URL) Yes Second page to capture (e.g., uncached or staging).
threshold number No 0.1 Pixelmatch threshold (0–1). Lower = more sensitive.
navTimeout integer (ms) No 60000 Max time to wait for initial navigation (page.goto).
idleMs integer (ms) No 1200 Required quiet window for waitForNetworkIdle.
settleMs integer (ms) No 1200 Extra pause after all waits to let last UI bits render.
imageWaitMs integer (ms) No 8000 Max time to wait for all <img> to be complete.
fontsWaitMs integer (ms) No 8000 Max time to wait for document.fonts.ready (icons/webfonts).
lcpWaitMs integer (ms) No 2500 Brief, best-effort wait for Largest Contentful Paint.
Tip: If icons or late JS are missing in screenshots, bump idleMs and settleMs to 2500–3000.

What the API does (per URL & device)

  1. Navigate (DOM ready)
  2. Wait for: full load → network idle → fonts ready → images complete → brief LCP → settle pause
  3. Take full-page screenshot
  4. Compute pixel diff (if dimensions match; otherwise show "side-by-side" note)
  5. Build a shareable HTML report embedding images/diff

Example Requests

A) Cached vs Uncached

GET /v1/visual/?url1=https://orthodoxatlas.com/monastery/the-three-holy-hierarchs-monastery-iasi/&url2=https://orthodoxatlas.com/monastery/the-three-holy-hierarchs-monastery-iasi/?nowprocket&settleMs=2500&idleMs=2500&navTimeout=90000

B) Staging vs Production

GET /v1/visual/?url1=https://staging.example.com/&url2=https://example.com/

Sample Success Response (200)

{
  "success": true,
  "jobKey": "visual_1730448123456",
  "result": {
    "urls": {
      "base": "https://example.com/",
      "compare": "https://example.com/?nowprocket"
    },
    "desktop": {
      "url1": "https://api.validatorhq.com/screenshots/screenshot-example_com-desktop-url1-1730448123456.png",
      "url2": "https://api.validatorhq.com/screenshots/screenshot-example_com-desktop-url2-1730448123456.png",
      "diff": "https://api.validatorhq.com/screenshots/diff-example_com-desktop-1730448123456.png",
      "summary": {
        "passed": false,
        "message": "Layout difference: 1245 pixels differ.",
        "pixelsDifferent": 1245,
        "percentDifferent": "0.37%"
      }
    },
    "mobile": {
      "url1": "https://api.validatorhq.com/screenshots/screenshot-example_com-mobile-url1-1730448123456.png",
      "url2": "https://api.validatorhq.com/screenshots/screenshot-example_com-mobile-url2-1730448123456.png",
      "diff": null,
      "summary": {
        "passed": false,
        "message": "Layout dimensions differ. See screenshots side-by-side."
      }
    },
    "reportUrl": "https://api.validatorhq.com/screenshots/report-example_com-1730448123456.html",
    "debugHtml": [
      { "label": "desktop-url1", "url": "https://api.validatorhq.com/screenshots/html-dump-example_com-desktop-url1-1730448123456.html" }
    ]
  }
}
Field notes:
  • result.desktop|mobile.url1/url2 — public URLs to PNG screenshots.
  • diff — PNG diff if dimensions match; otherwise null.
  • summary.passedtrue when pixelsDifferent === 0.
  • reportUrl — shareable HTML with side-by-side images and diff.
  • debugHtml — HTML dumps saved on timeouts/errors for debugging.

Error Codes

HTTP When Body (example)
400 Missing required params {"error":"Missing ?url1= and/or ?url2="}
500 Navigation/screenshot failure {"error":"Navigation failed: net::ERR_BLOCKED_BY_CLIENT"}

Report Layout

  • Header: URL 1, URL 2, created time, threshold, and links to any HTML dumps
  • Desktop: URL 1 image, URL 2 image, Diff (or dimension mismatch note), badges for status & percentage
  • Mobile: same as Desktop
  • Clean, print-friendly HTML (no external CSS/JS), responsive grid

Tips & Best Practices

  • Stability knobs: Start with settleMs=2500&idleMs=2500 when sites lazy-load icons/content.
  • Third-party noise: If analytics/chat keeps the network "busy," the fixed settleMs ensures progress.
  • Dimension mismatches: Often due to consent bars, sticky headers, AB tests. The report still shows side-by-side shots.
  • Threshold tuning: 0.05 catches tiny shifts; 0.15–0.20 ignores subtle anti-aliasing.
  • Mobile profile: iPhone 12 by default (fallback viewport 390×844, DPR 3).

Changelog

  • v2: Introduced url1/url2; robust waits for fonts/images/LCP; improved HTML report; desktop+mobile in one call.
  • v1 (legacy): Single url input with implicit ?nowprocket comparison.