ScreenshotNeo

BlogHow-to

How to Run Reg-suit Only on Changed Pages in a Pull Request

Reg-suit compares screenshots you provide; select affected routes in your CI capture step, then run the usual comparison and reporting flow.

By the ScreenshotNeo team4 October 20268 min read

Short answer: Reg-suit compares screenshot images supplied to it; it does not determine which application routes are affected by changed source files. To run visual regression checks only on changed pages, add route selection to your CI screenshot-generation step: diff the pull request against its intended base, map changed paths to routes, capture only those routes into Reg-suit’s actual-image directory, then run the normal npx reg-suit run flow.

This guide shows a GitHub Actions example with an explicit path-to-route map. You must adapt that map to your application: Reg-suit cannot infer your route dependencies, and a shared component or global stylesheet may affect many pages.

1. Understand which step selects pages

Reg-suit is a command-line visual regression testing tool. Its run flow syncs expected snapshots, compares images, publishes results, and can notify configured integrations. Its Git hash key-generation plugin selects a comparison point by walking the Git branch graph. The documented workflow does not automatically map changed source files to routes.

reg-cli, which performs image comparisons, takes actual and expected image directories as inputs. Consequently, limit the pages by generating a smaller set of actual images before invoking Reg-suit. Keep the file naming and directory conventions compatible with your existing expected snapshots and configuration.

2. Define a route dependency map

Start with a deliberate mapping from source paths to routes. The example below assumes a small application with routes /pricing, /docs, and /account. Replace these paths and rules with your own route structure.

# visual-routes.json
{
  "src/pages/pricing/": ["/pricing"],
  "src/pages/docs/": ["/docs"],
  "src/pages/account/": ["/account"],
  "src/components/": ["/pricing", "/docs", "/account"],
  "src/styles/": ["/pricing", "/docs", "/account"]
}

Include shared layouts, design tokens, global styles, shared components, and data sources wherever they can change rendered output. A change to a route-specific file can map to one route; a change to a shared dependency may need to map to every route that uses it. This relationship is specific to the application.

3. Select affected routes and capture them

The following Python script reads changed paths from a file, applies the map, and invokes an existing screenshot command once per selected route. It writes screenshots to a configurable actual-image directory. The screenshot command is intentionally an application-specific placeholder: replace it with your browser automation command and ensure it writes files using the same names as your expected snapshots.

#!/usr/bin/env python3
# scripts/capture_changed_routes.py
import json
import os
import subprocess
import sys
from pathlib import Path

if len(sys.argv) != 3:
    raise SystemExit("usage: capture_changed_routes.py changed-files.txt actual-dir")

changed_file = Path(sys.argv[1])
actual_dir = Path(sys.argv[2])
rules = json.loads(Path("visual-routes.json").read_text())
changed_paths = [line.strip().replace("\\", "/") for line in changed_file.read_text().splitlines() if line.strip()]
routes = set()

for changed in changed_paths:
    for prefix, mapped_routes in rules.items():
        if changed.startswith(prefix):
            routes.update(mapped_routes)

if not routes:
    print("No routes selected by the dependency map.")
    # Explicit policy: an empty selection is a successful no-op.
    raise SystemExit(0)

actual_dir.mkdir(parents=True, exist_ok=True)
for route in sorted(routes):
    # Replace this with your project's screenshot command.
    # The command should save a snapshot named for the route in actual_dir.
    filename = route.strip("/").replace("/", "-") or "home"
    output_path = actual_dir / f"{filename}.png"
    subprocess.run([
        "npm", "run", "capture-page", "--",
        "--url", os.environ["APP_BASE_URL"] + route,
        "--output", str(output_path),
    ], check=True)
    print(f"Captured {route} -> {output_path}")

This example uses a prefix map for clarity. If your repository has nested packages, generated routes, dynamic segments, or route-level data dependencies, use a more precise mapping or maintain route ownership metadata alongside the application. The key requirement is that the selected actual screenshot names match the expected snapshot keys Reg-suit will compare.

4. Wire selection into GitHub Actions

Fetch the base and head commits needed by both the diff and Reg-suit’s Git hash plugin. GitHub’s pull request and compare pages can use different merge-base handling, so choose the base/head interpretation intentionally. For a pull request event, this example uses the event’s base SHA and head SHA directly.

name: Visual regression
on:
  pull_request:

jobs:
  visual-regression:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - uses: actions/setup-python@v5
        with:
          python-version: "3.x"

      - run: npm ci

      - name: Find changed paths
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
        run: git diff --name-only --find-renames "$BASE_SHA" "$HEAD_SHA" > changed-files.txt

      - name: Build application
        run: npm run build

      - name: Start application
        run: |
          npm run start -- --host 127.0.0.1 > app.log 2>&1 &
          for attempt in $(seq 1 60); do
            if curl --fail --silent http://127.0.0.1:3000/ >/dev/null; then exit 0; fi
            sleep 1
          done
          cat app.log
          exit 1

      - name: Capture selected routes
        env:
          APP_BASE_URL: http://127.0.0.1:3000
        run: python scripts/capture_changed_routes.py changed-files.txt actual

      - name: Compare and publish snapshots
        run: npx reg-suit run

Adapt checkout and diff behavior to your event and branch model. For example, if your intended comparison is against a merge base rather than the recorded base commit, calculate that explicitly and use the resulting revision consistently. Renames and deletions need a policy: a rename may affect both the old and new route ownership, while a deleted route may need its obsolete expected snapshot removed through your normal baseline maintenance process.

5. Decide how empty and incomplete selections behave

  • No mapped route: Decide whether the job should succeed as a no-op, fail to force a map update, or fall back to capturing all routes. The sample exits successfully. Many teams choose a full capture for unknown paths to avoid silently missing visual effects.
  • Missing actual image: Treat this as a capture or route-selection failure, not as a valid unchanged result. Confirm the route was mapped, the server was ready, and the screenshot command wrote the expected filename.
  • Deleted route: Do not try to capture a route that no longer exists. Decide how to remove or update the matching expected image in the baseline workflow.
  • Shared or unknown files: Map global dependencies broadly. If the mapping cannot confidently exclude affected pages, capture all routes.
  • Dynamic pages: Map a route family to representative, stable fixture URLs, and ensure the same state and data are used for actual and expected captures.

6. Keep Reg-suit comparison and reporting intact

After generating the selected actual images, run npx reg-suit run with your existing configuration. Preserve the snapshot key-generation and storage setup so the comparison targets the intended baseline. The Reg-suit README documents GitHub Actions usage with full history and notes a detached-HEAD workaround for CI environments.

Reg-suit documents notification integrations including its GitHub app and notifier plugin, as well as plugins for GitLab, Slack, and Chatwork. Configure the integration appropriate to your repository and verify its setup against the project documentation. The separate reg-actions project is an alternative action-based route for reporting and pull request comments; it does not mean the core CLI derives affected application routes automatically.

7. Performance, reliability, and cost

Reducing captured routes can reduce browser work and the number of images compared, but the actual benefit depends on your application and capture setup. No universal timing or cost reduction follows from the Reg-suit documentation. Keep route selection conservative around shared dependencies: a broader capture takes more work, while an omitted affected route weakens coverage.

For reliability, pin and maintain your CI action and runtime versions according to your repository policy, wait for the application to become ready before capturing, fail the job on capture errors, and preserve logs when a screenshot step fails. Use deterministic fixtures, viewport settings, and screenshot names across baseline and pull request runs. Ensure the checkout contains the revisions needed both for the diff and for Reg-suit’s Git history-based key generation.

8. Troubleshooting

Symptom Likely cause Fix
No pages are captured Changed paths do not match map prefixes, or diff output is empty. Print changed-files.txt in CI logs; verify base and head SHAs and update the map.
A shared change misses visual regressions Shared components, layouts, styles, or data dependencies are absent from the map. Map those paths to every dependent route, or use a full-capture fallback for unknown paths.
Reg-suit reports missing snapshots The capture script output names or directories differ from the expected snapshot convention, or a route failed. Compare actual and expected paths; make the capture step fail if any selected route does not produce an image.
Git hash plugin cannot resolve a comparison key The checkout lacks the required commit history or the CI checkout is detached in an unsupported way. Fetch full history as in the documented Actions setup and apply the project’s documented detached-HEAD workaround.
Unexpected routes are selected A broad prefix or overlapping rule maps a file more widely than intended. Review matching rules and log the reason each route was selected; retain broad rules only for genuinely shared dependencies.
Renamed or deleted files produce confusing results The diff and route map do not define rename/deletion behavior. Use rename-aware diff handling and explicitly decide whether old and new ownership, route removal, or a full capture applies.
Capture sees a blank or partially loaded page The app was not ready, or page-specific asynchronous content had not settled. Wait for a reliable readiness condition in the screenshot command and inspect the app logs and route response.

Or skip the browser setup

If you need screenshots in a script or CI job without managing browser capture infrastructure, ScreenshotNeo provides a website screenshot API and MCP server. Its one-call API can return an image or PDF; for a route, pass its full URL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

For a route from your application, replace the target URL with the full route URL. See the ScreenshotNeo API documentation for request options. ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. For visual regression, still apply your own changed-route selection and baseline naming rules.

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Does Reg-suit have a built-in changed-page option?

The reviewed project documentation describes image inputs, comparison, snapshot keys, CI, and integrations; it does not document automatic source-file-to-route selection. Implement that selection in your capture or input-preparation step.

Can I run only reg-cli on the selected images?

Reg-cli compares actual and expected image directories and can generate a report. Use the documented Reg-suit run flow when you also need its sync, publishing, and configured notification steps.

Should an unmapped change skip visual regression?

Choose a repository policy. A successful no-op is efficient but can miss effects; a full-capture fallback or a failing map check is safer when the impact is unknown.

Sources