Visual regression testing tool — screenshots stored in Cloudflare R2, comparison reports saved locally and uploaded to R2 for public sharing.
Follow these steps once before using this project for the first time.
Go to dash.cloudflare.com and sign up if you don't have an account.
- In the dashboard sidebar, click R2 Object Storage
- Click Purchase R2 Plan and add a payment method (required even for the free tier)
- Free tier includes: 10 GB storage, 1M writes/month, 10M reads/month, no egress fees
Your Account ID is visible in the URL of any Cloudflare dashboard page:
https://dash.cloudflare.com/<account-id>/...
Copy it — you'll need it for .env.
- Go to https://dash.cloudflare.com/<account-id>/api-tokens
(replace<account-id>with yours) - Click Create Token
- Click Create Custom Token (at the bottom of the template list)
- Give it a name, e.g.
someproject-visual-test - Under Permissions, set the three dropdowns to:
Account → Workers R2 Storage → Edit - Click Continue to Summary → Create Token
- Copy the token immediately — it is only shown once
Copy .env.example to .env and fill in your values:
cp .env.example .envCF_ACCOUNT_ID=your_account_id
CF_API_TOKEN=your_api_token
R2_BUCKET=someproject-visual-test
SITEMAP_INDEX=https://www.example.com/sitemap.xml
R2_PUBLIC_URL=https://pub-xxxx.r2.dev # fill in after step 6 belowThe bucket (someproject-visual-test) is created automatically on first run.
After running the project for the first time:
- Go to R2 →
someproject-visual-test→ Settings → Public Development URL - Click Enable → type
allow→ click Allow - Copy the URL shown (e.g.
https://pub-xxxx.r2.dev) - Paste it into
.envasR2_PUBLIC_URL
All future comparison reports will print a public link automatically.
cd someproject-visual-test
npm installGoogle Chrome must be installed at the default macOS path (/Applications/Google Chrome.app).
Override with CHROME_PATH in .env if needed.
node fetch-sitemap.jsCrawls the sitemap index, collects all page URLs, saves them to sitemap.txt.
Re-run any time the site structure changes.
node take-screenshots.js- Takes a full-page screenshot (1280 px wide) of every URL in
sitemap.txt - Uploads each PNG directly to R2 at
screenshots/<timestamp>/ - No local copies are kept
To preview which URLs would be captured without actually running:
node take-screenshots.js --dry-runnode take-screenshots.jsnode list-screenshots.jsOutput example:
2026-08-07_09-00-00 (806 pages)
2026-08-07_16-30-00 (806 pages)
node compare-screenshots.js 2026-08-07_09-00-00 2026-08-07_16-30-00This will:
- Download both screenshot sets from R2
- Pixel-diff every page
- Save diff images to R2 at
diffs/<before>_vs_<after>/ - Upload the full report (HTML + images) to R2 at
reports/<before>_vs_<after>/ - Write a local copy to
reports/<before>_vs_<after>/index.html - Print a public shareable URL (if
R2_PUBLIC_URLis set in.env)
Open the local report:
open reports/2026-08-07_09-00-00_vs_2026-08-07_16-30-00/index.htmlnode storage.jsShows total used space, breakdown by prefix (screenshots / diffs / reports), and remaining free tier capacity (10 GB free).
Preview what would be deleted:
node delete-set.js 2026-08-07_09-00-00Actually delete (screenshots + all associated diffs and reports for that timestamp):
node delete-set.js 2026-08-07_09-00-00 --confirmsomeproject-visual-test/ ← R2 bucket
screenshots/
2026-08-07_09-00-00/
index.png
about_.png
...
2026-08-07_16-30-00/
...
diffs/
2026-08-07_09-00-00_vs_2026-08-07_16-30-00/
about_.png ← diff images for changed pages only
...
reports/
2026-08-07_09-00-00_vs_2026-08-07_16-30-00/
index.html ← shareable report (if public access enabled)
before/
after/
diff/
reports/
2026-08-07_09-00-00_vs_2026-08-07_16-30-00/
index.html ← open in browser (no server needed)
before/ ← before images for changed pages
after/ ← after images for changed pages
diff/ ← pixel-diff images
| Variable | Description |
|---|---|
CF_ACCOUNT_ID |
Cloudflare Account ID (from dashboard URL) |
CF_API_TOKEN |
Cloudflare API token with Workers R2 Storage: Edit |
R2_BUCKET |
R2 bucket name (default: someproject-visual-test) |
SITEMAP_INDEX |
Sitemap URL to crawl |
R2_PUBLIC_URL |
Public dev URL from R2 bucket settings (e.g. https://pub-xxxx.r2.dev) |
BASIC_AUTH_USER |
Basic auth username (optional) |
BASIC_AUTH_PASS |
Basic auth password (optional) |
CHROME_PATH |
Path to Chrome (only needed if not at default macOS location) |
| Script | Description |
|---|---|
node fetch-sitemap.js |
Fetch URLs from sitemap → sitemap.txt |
node take-screenshots.js |
Take screenshots → upload to R2 |
node list-screenshots.js |
List available timestamps in R2 |
node compare-screenshots.js <before> <after> |
Diff two sets → local + R2 report |
node storage.js |
Show R2 storage usage vs free tier |
node delete-set.js <ts> |
Preview deletion of a set (add --confirm to execute) |