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)
- Navigate (DOM ready)
- Wait for: full load → network idle → fonts ready → images complete → brief LCP → settle pause
- Take full-page screenshot
- Compute pixel diff (if dimensions match; otherwise show "side-by-side" note)
- 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; otherwisenull.summary.passed—truewhenpixelsDifferent === 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=2500when sites lazy-load icons/content. - Third-party noise: If analytics/chat keeps the network "busy," the fixed
settleMsensures progress. - Dimension mismatches: Often due to consent bars, sticky headers, AB tests. The report still shows side-by-side shots.
- Threshold tuning:
0.05catches tiny shifts;0.15–0.20ignores 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
urlinput with implicit?nowprocketcomparison.