Docker-based visual regression testing with Playwright, a web UI, and optional AI analysis.
- Cross-browser capture (Chromium/WebKit) with multi-viewport baselines
- Fast review UI with diff overlays and bulk approvals
- Fullscreen viewer with multi-column mode for long pages
- Cross-compare reports (browser vs browser)
- Optional AI analysis for quick triage
- Capture consistent screenshots across browsers and viewports.
- Review diffs in a fast UI built for triage.
- Automate approvals and cross-browser comparisons.
- Built with AI assistance (Claude Opus 4.5 and Codex 5.2), reviewed and tested manually.
- Currently tested on macOS with Docker Desktop, should work elsewhere but feedback is welcome. Open issues or PRs.
- Thanks BackstopJS for the real project and inspiration!
git clone https://github.com/your-org/vrtini.git
cd vrtini
npm install
npm run build
npm linknpm install -g @your-org/vrtinicd /path/to/your/project
vrtini init
vrtini build
vrtini test
vrtini serveOpen the UI at http://127.0.0.1:4173 (or http://localhost:4173).
To expose on LAN (--host 0.0.0.0), set VRT_AUTH_TOKEN first.
If you intentionally want an unauthenticated remote bind (not recommended), set VRT_ALLOW_INSECURE_REMOTE=1.
Config files live at the project root and follow this naming convention:
vrtini.config.json— the default configvrtini.<profile>.config.json— a named profile (shows up as a tab in the web UI)
Examples included:
vrtini.config.minimal.json— smallest valid configvrtini.config.example.json— every option documentedvrtini.base.config.example.json+vrtini.chrome.config.example.json—extendspattern
Key options:
browsers,viewportsscenarioDefaultsand per-scenario overridesenginesand thresholdscrossComparefor browser-to-browser diffs
Put as many vrtini.<name>.config.json files at the root as you need (e.g., vrtini.chrome.config.json, vrtini.cross-webkit.config.json). Each profile:
- Is auto-discovered by the web UI and listed as a sidebar tab.
- Can be selected on the CLI via
-c ./vrtini.<name>.config.json. - Writes to its own artifacts directory by default:
./.vrtini/<name>/baselinesand./.vrtini/<name>/output, so profiles never clobber each other. Override by settingbaselineDir/outputDirexplicitly.
Any config may declare an extends field (string or array) to inherit from another file:
{
"extends": "./vrtini.base.config.json",
"browsers": [{ "name": "chromium", "version": "130" }]
}Paths are resolved relative to the file that declares extends. Merge rules: objects deep-merge, arrays replace, primitives replace. Chains are supported. Circular references throw.
This lets you factor viewports, engines, AI settings, and shared scenarios into a base file and keep each profile lean.
| Command | Description |
|---|---|
vrtini init |
Create example config |
vrtini build |
Build Docker images for your config |
vrtini test |
Capture screenshots and compare |
vrtini test -s <name> |
Test specific scenario(s) |
vrtini test -q |
Quick mode (skip expensive engines) |
vrtini approve |
Approve scenario as baseline |
vrtini approve --all |
Approve all as baselines |
vrtini cross-compare |
Compare browser pairs |
vrtini report |
Regenerate HTML report |
vrtini serve |
Start the web UI |
- Project dashboard and image gallery
- Fullscreen viewer with compare modes
- Bulk approvals and filtering
- Cross-compare results with diff overlays
- Optional AI analysis panel
Use this when you only need one browser/project as your source of truth.
Typical flow:
- Create one project with multiple scenarios/URLs.
- First run:
Run Testscreates the initial screenshots. - Review and approve them as baselines.
- Change the app.
- Re-run
Run Tests. - Review diffs and approve or reject them.
Rule of thumb:
- first run = baseline creation
- later runs = regression detection against approved baselines
If the app is simple and you do not need browser-to-browser auditing, tests alone are enough for day-to-day regression checking.
Use this when Chromium latest is your source of truth, but you also need to keep Chromium old, WebKit latest, or WebKit old visually aligned.
Recommended project setup:
Latest Full (chromium)for the main reference baseline- extra projects for legacy/alternate browsers with their own approved baselines
Cross Compareprojects for browser-vs-browser auditing
There are two common workflows here.
This is the steady-state workflow after the system is already aligned.
- Change the app.
- Run tests for
Latest Full (chromium). - Run tests for the other browser projects.
- Review diffs and approve/reject as needed.
- Only run
Cross Comparewhen you want to audit browser drift again or investigate suspicious differences.
In this state, tests are the main safety net. Cross-compare becomes an auditing tool, not a mandatory step on every change.
This is the heavier workflow used while building backward compatibility.
- Capture and approve the initial baselines per browser project.
- Run
Cross Compareagainst the chosen source of truth (Chromium latestis the usual reference). - Investigate the highest diffs first.
- Patch app code, scenario config, or capture behavior until the alternate browser becomes visually acceptable.
- Re-run tests for the affected project.
- Re-run
Cross Compareto verify the reconciliation. - Repeat until the browser-specific projects are stable.
Once this alignment work is complete, go back to the simpler steady-state workflow:
- change app
- run tests for all browser projects
- review diffs
- use cross-compare only when you need cross-browser auditing again
| Key | Action |
|---|---|
1 |
Baseline |
2 |
Test |
3 |
Diff |
← → |
Navigate |
+ - |
Zoom |
0 |
Fit to screen |
A |
Approve current |
R |
Reject current |
Esc |
Close |
Copy .env.example to .env and set one of:
ANTHROPIC_API_KEY=...
OPENAI_API_KEY=...
AI runs are optional and can be disabled per project.
vrtini build # Build for your config
vrtini build --playwright 1.40.0 # Specific version
vrtini build --all-versions # All supported versionsUse host.docker.internal instead of localhost in URLs.
Use the helper container to run an older Playwright/Chromium build with a VNC UI. Tested on macOS with Docker Desktop.
docker build --platform=linux/amd64 -f docker/vnc/Dockerfile -t vrt-pw110-vnc docker/vnc
docker run --rm --platform=linux/amd64 \
-p 6080:6080 \
-e TARGET_URL=http://host.docker.internal:3000 \
vrt-pw110-vncThen open http://localhost:6080/vnc.html in your browser.
Optional: set BROWSER=webkit to launch WebKit instead of Chromium.
npm run build
npm run lint
npm run format:check
npm run typecheck
npm run test:unit
npm run test:uiGit hooks enforce secret scanning with gitleaks on commit (.husky/pre-commit) and run quality checks on push (.husky/pre-push: lint, format, typecheck, unit tests).
The pre-commit hook also runs lint-staged (ESLint + Prettier on staged files).
Install either:
brew install gitleaksor use Docker (the hook falls back to Docker if available).
Local loop script:
scripts/codex-loop.sh
Apache-2.0