Build Shotomatic
KINDAreplaces $7.99/mosaves $95.88/yrback to the verdict
A screenshot automation tool whose entire value is determinism: the same page captured twice must produce the same bytes, or the output cannot be diffed, reviewed or trusted, and almost everything about a modern page conspires against that.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Orchestrates capture, comparison and decoration.
Get it python.org or your package manager, then a virtual environment.
Verify
python3 --version prints 3.12 or higher - installfree
Why A browser update changes font rendering and layout subtly, which invalidates every baseline at once and looks like your site changed.
Get it A browser automation library with a pinned browser build, recorded in your configuration.
Verify
The browser version is pinned and reported in every run - have readyfree
Why Determinism only fails on real pages, and the failures are specific: animation, a live clock, lazy images, web fonts and third-party content.
Get it Collect pages exhibiting each, from a site you own or have permission to capture.
Verify
All five exhibit their problem on a naive capture - have readyfree
Why Localised capture is the main reason to automate screenshots, and half-translated output is the failure that looks correct.
Get it A real application with genuine translations, including one language substantially longer than English.
Verify
Four locales render with translated dates and numbers, not only text - decidefree
Why A baseline updated automatically makes the entire comparison meaningless, and deciding the process after the first noisy run means deciding it while annoyed.
Get it Decide who approves a baseline change and what tolerance is acceptable, and write it down.
Verify
A written process exists and tolerance is configured
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- scripts(id, name, steps_json, viewport_json, wait_strategy, created_at) - targets(id, script_id, url, locale, theme, device_profile) - captures(id, target_id, run_id, image_path, content_hash, captured_at, attempt, stable) - runs(id, started_at, finished_at, browser_version, succeeded, failed, non_deterministic_count) - baselines(target_id, content_hash, approved_at, approved_by) · what the output is supposed to look like - diffs(capture_id, baseline_hash, changed_pixels, changed_regions_json, verdict) - A capture records whether it was stable, meaning two consecutive captures produced identical bytes, because an unstable capture cannot be compared to anything.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
BROWSER_VERSION | required | pinned build identifier | Recorded per run. A browser update invalidates every baseline at once. |
OUTPUT_DIR | required | ./captures | Raw captures and decorated outputs, kept separately. |
FREEZE_CLOCK_AT | required | 2026-01-01T12:00:00Z | A fixed time, so any visible clock renders identically. |
BLOCK_EXTERNAL_RESOURCES | required | always | Present as a constant. Third-party content is never the same twice. |
STABILITY_CAPTURES | required | 2 | Each target captured this many times and compared before being used. |
DIFF_TOLERANCE_PIXELS | required | 0 | Zero by default. Tolerance hides the changes you are looking for. |
The build, in order
The same page twice, identically
Capturing a page twice produces byte-identical images.
Two of the four things that make a page render differently each run.
Files
src/determinism.pyAdvertisements and third-party widgets are never the same twice.
Files
src/determinism.pyThe stability check is what makes every later diff meaningful.
Files
src/capture.pytests/test_stability.py
done when · tick each as it passeswatch out- Everything on a modern page fights determinism: a timestamp, a rotating advertisement, a fade-in that has not finished, a font arriving late, a randomised placeholder. Capture twice and compare on every run, so an unstable target is detected rather than producing a diff you spend an hour investigating.
Knowing when the page is ready
Captures happen after the page has finished, not after a guessed delay.
Fonts and network, not a number of seconds.
Files
src/wait.pyThe page knows when it is ready better than any generic rule.
Files
src/scripts.pyA screenshot of a loading spinner is worse than a failure.
Files
src/capture.py
done when · tick each as it passeswatch out- Waiting a fixed number of seconds is either too short, producing captures of half-loaded pages, or too long, making a batch take hours, and it is both on different pages in the same run. Wait on signals, and treat a page that never settles as a failure rather than capturing whatever was there at the timeout.
The same shot in every language
A script runs across locales and the text is genuinely translated.
Browser locale, header and application preference together.
Files
src/locale.pyGerman is longer and the button is not.
Files
src/overflow.pyComparing a German screenshot to an English baseline finds nothing useful.
Files
src/baselines.py
done when · tick each as it passeswatch out- Setting a language through a query parameter while the browser still reports a different locale produces pages that are half translated, with dates and numbers in the original language, and it looks correct enough to ship. Set the browser locale, the accept header and any application preference together.
Diffs a person can review quickly
A change is shown as a region rather than a pixel count.
A percentage cannot distinguish a missing button from antialiasing.
Files
src/diff.pyAutomatic baseline updates make the whole comparison pointless.
Files
src/baselines.pyAn unstable target's diff is not a change and should not be reviewed as one.
Files
src/diff.py
done when · tick each as it passeswatch out- Reporting a diff as a percentage of changed pixels tells you nothing about whether the change matters, since a one percent change might be an entire button disappearing or an antialiasing difference along one edge. Report bounding regions and show them, so a reviewer looks at the change rather than at a number.
Frames, annotations and batchesproduct builder
Hundreds of decorated screenshots produce unattended and reproducibly.
So a wording change is a re-render rather than a recapture.
Files
src/decorate.pyReproducible, and reviewable as text.
Files
src/decorate.pyOne bad page must not end a three hundred target run.
Files
src/batch.py
done when · tick each as it passeswatch out- Baking a caption into the capture means changing the wording requires recapturing every screenshot, which for a localised app store set is hundreds of captures and a browser version that may have changed since. Keep the raw capture and apply decoration as a separate reproducible step.
That is the whole plan for Shotomatic. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Full visual testing with component snapshots.
- Interaction recording as video.
- Cross-browser rendering comparison.
- Semantic change detection.
- polished display, window, and custom-area selection
- reliable macOS permission setup and recovery
- Action Capture guide editing and annotations
- batch website capture with per-URL settings
- integrated PDF, ZIP, and MP4 export workflows
- Capturing on a schedule to detect unannounced third-party changes, which is a real use
- Store-ready output sizes per platform, which is the tedious part of a release
- A review interface for approving a batch of baseline changes at once
Need the files? The project pack on the verdict page hands your agent the whole brief · more screenshots.