Recommended Free Tools
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
reg-suit does not take screenshots. It compares image files, produces an HTML report of the differences, and can publish the images and report to storage. To use it with an Angular website, you need three pieces: a capture step that writes PNG screenshots of fixed pages or component states into one folder, a reg-suit configuration whose actualDir points at that folder, and a CI job that runs npx reg-suit run after the capture finishes. The reg-suit project’s own examples pair it with Puppeteer for full pages and with Storybook-based screenshot tooling for components, and this guide uses Puppeteer for the main path and covers Storybook where it fits.
What you need before you start
- An Angular application that builds and runs both on your machine and in CI. A production build served as static files is the most repeatable target for capture (see the server step below).
- Node.js and npm. Puppeteer downloads a matching Chromium during installation, so check the Node.js requirements of the Puppeteer release you install rather than assuming an older Node version still works.
- A Git repository, if you want reg-suit to derive comparison keys from commit hashes, which is the plugin setup used in the reg-suit examples.
- Optional storage for published results. The reg-suit examples use an AWS S3 bucket. S3 is one documented option, not a requirement.
- A CI system with a secret store for any storage credentials. Do not commit access keys to the repository.
How the pieces fit together
Each visual regression run has two stages, and they should be kept separate in your pipeline.
- Capture. A browser opens each page or story at a fixed viewport, waits until the Angular content is rendered, and writes a screenshot file into a known directory.
- Compare and publish. reg-suit reads that directory, selects the expected images it should compare against, writes an HTML report of the differences, and publishes the results through the plugins you configure.
If the capture step fails or writes files to a different place than actualDir, reg-suit has nothing valid to compare. Most setup problems come down to that mismatch, so the rest of this guide keeps the directory names explicit.
Step 1: Install reg-suit and Puppeteer
- From the root of your Angular project, install the tools as development dependencies:
npm install --save-dev reg-suit puppeteer - Run the reg-suit initializer. It asks for the actual image directory and other project settings, and it installs and configures the plugins you select:
npx reg-suit initEnter
screenshotsas the actual image directory if you are following this guide. You can change it later, as long as the capture script and the configuration agree. - Open the generated configuration file (reg-suit uses
regconfig.jsonin the project root by default) and make sure the core settings look like this:{ "core": { "workingDir": ".reg", "actualDir": "screenshots", "thresholdRate": 0, "plugins": { "reg-keygen-git-hash-plugin": true } } } - Add the generated folders to
.gitignoreso that per-run output is not committed:.reg/ screenshots/
Some notes on these values. workingDir is where reg-suit keeps its working files and the report. actualDir is the folder your capture step writes to, and it must match exactly. thresholdRate is the proportion of differing pixels that is tolerated before an image is treated as changed. Starting at 0 makes every visible change a review item, which is the strictest and the easiest to reason about; raise it only after you have seen real noise from anti-aliasing or font rendering. The Git-hash key plugin is the example used by the reg-suit project; if your branch model needs a different key strategy, choose another key-generation plugin and check its README.
#1 Best Overall
Step 2: Capture Angular pages with Puppeteer
Serve a production build for capture
Capture works best against a stable build, not the development server, because the dev server keeps live-reload connections open and makes network-idle detection unreliable. Build the app, then serve the output folder with a static server that falls back to index.html for unknown routes, so deep links such as /pricing load the Angular router instead of returning 404. On recent Angular versions the browser build is written to dist/your-app/browser; older versions write directly to dist/your-app, so check your build output.
npx ng build --configuration production
npx http-server dist/your-app/browser -p 8080 -s -P http://localhost:8080?
A capture script that waits for Angular to render
Save this as capture.js in the project root. It visits a list of routes, disables animations and transitions, waits for the application to bootstrap and fonts to load, and writes one PNG per route into screenshots/.
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const BASE_URL = process.env.APP_URL || 'http://localhost:8080';
const OUT_DIR = path.resolve('screenshots');
const VIEWPORT = { width: 1280, height: 800, deviceScaleFactor: 1 };
const PAGES = [
{ name: 'home', route: '/' },
{ name: 'pricing', route: '/pricing' },
{ name: 'docs-getting-started', route: '/docs/getting-started' },
];
const FREEZE_CSS = `
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
`;
(async () => {
fs.mkdirSync(OUT_DIR, { recursive: true });
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport(VIEWPORT);
await page.emulateMediaFeatures([
{ name: 'prefers-reduced-motion', value: 'reduce' },
]);
for (const { name, route } of PAGES) {
await page.goto(BASE_URL + route, {
waitUntil: 'networkidle0',
timeout: 60000,
});
// Angular has bootstrapped and rendered content into the root component.
await page.waitForSelector('app-root:not(:empty)', { timeout: 30000 });
// Application-specific readiness signal, if your app sets one.
await page.waitForFunction(() => window.__appReady === true, {
timeout: 30000,
}).catch(() => {});
await page.evaluate(() => document.fonts.ready);
await page.addStyleTag({ content: FREEZE_CSS });
await page.screenshot({
path: path.join(OUT_DIR, `${name}.png`),
fullPage: true,
});
console.log(`captured ${name}`);
}
} finally {
await browser.close();
}
})().catch((err) => {
console.error(err);
process.exit(1);
});
The script does several things that matter for stable comparisons:
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesRank #2
- It waits for a real render condition.
networkidle0covers pending requests,app-root:not(:empty)confirms Angular has rendered, and the optionalwindow.__appReadyflag lets you signal the exact moment the view is ready. The flag is something you add to the application yourself, for example after your data load completes. The.catchkeeps the script running if your app does not set the flag, so remove it if you want a missing signal to fail the job. - It freezes motion before the shot. Animations and transitions produce different pixels on each run. Disabling them in the page is simpler and more reliable than trying to time them.
- It fixes the viewport and device scale. Keep the same width, height and scale in every run, and in the baseline run, or every pixel comparison will report a change.
Run it against the server from the previous step:
APP_URL=http://localhost:8080 node capture.js
Expected output is one captured line per route and a set of PNG files in screenshots/. Open a few of them before you go further: a file with only a blank white page means the wait conditions did not hold, which is covered in the troubleshooting section.
Capturing logged-in or stateful pages
The script above only covers public routes. For pages behind login, do the sign-in inside the same Puppeteer session before the first page.goto, using page actions such as page.type and page.click against your login form, or set a session cookie with page.setCookie if your staging environment accepts one. Keep test accounts and their passwords in CI secrets, not in capture.js. For state that lives in the Angular application, such as a selected tab or a seeded list, drive the UI with clicks and waits in the same way, and record the exact sequence so the same states are captured on every run.
Storybook for component-level coverage
Storybook is the better choice when the goal is to review individual component states, such as a button in each variant or a form with validation errors, without navigating through the whole application. Storybook’s Angular visual testing documentation treats stories as repeatable test specifications, which is the property a screenshot comparison needs. reg-suit’s examples include an Angular project that uses Storybook with screenshot tooling, so the combination is a documented shape.
Rank #3
Verify the capture add-on before you adopt it. The storybook-chrome-screenshot repository lists an Angular demo, but its own feature list still shows Angular support as a to-do, so the demo’s existence does not confirm compatibility with your Angular and Storybook versions. The storycapture add-on describes Puppeteer-based screenshot generation for visual testing and says it is framework-independent, including Angular, but you should check its current maintenance status and version compatibility yourself. Once a capture add-on works, the reg-suit side is unchanged: point actualDir at the folder the add-on writes to and run the same comparison.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Choosing between Puppeteer and Storybook
| Decision point | Puppeteer against the running app | Storybook stories |
|---|---|---|
| Coverage target | Full routes and pages, including layout between components | Isolated component states, one story per state |
| State setup | Requires navigation, login and UI actions in the script; more moving parts | Each story sets its own inputs and data, which is usually more deterministic |
| Compatibility risk | Depends on the Puppeteer and Chromium versions you install | Depends on the Storybook version and on the chosen capture add-on’s Angular support |
| Maintenance | One script to keep current with routes | Stories must be written for each state, and the add-on must be kept compatible |
| Typical use | Catching regressions in page-level layout and routing | Reviewing design-system components in isolation |
Many teams use both: Storybook for the component library and a short Puppeteer list for the most important full pages. Both write PNG files into the same reg-suit folder structure if you keep separate subdirectories and use the same actualDir rules.
Step 3: Run the comparison in CI
In CI, the capture step must finish before reg-suit runs. The reg-suit project’s Puppeteer example follows the same order: check out the code, install dependencies, run the capture script, then run reg-suit. The example uses an older CI configuration, so treat the following workflow as a current template and confirm the action versions against your own CI provider.
Rank #4
name: visual-regression
on:
pull_request:
push:
branches: [main]
jobs:
visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx ng build --configuration production
- name: Start static server
run: |
npx http-server dist/your-app/browser -p 8080 -s -P http://localhost:8080? &
for i in $(seq 1 30); do curl -sf http://localhost:8080 && break; sleep 1; done
- name: Capture screenshots
run: APP_URL=http://localhost:8080 node capture.js
- name: Compare and publish
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
run: npx reg-suit run
The fetch-depth: 0 setting makes the full Git history available, which the Git-hash key plugin needs to find the right baseline. The S3 credentials are read from the CI secret store. If you do not publish to S3, remove the environment block and the publisher configuration, and run the comparison alone.
Step 4: Review the baseline and the first run
The first run in a project has no earlier expected images. reg-suit reports the captured images as new items, and the published result becomes the expected image set for later runs. Expect the first run to look different from every later run, and do not treat its “new” items as failures.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →- Open the HTML report produced by the run. The report is written under the working directory configured in
regconfig.json(.regin this guide) and is also published if you configured a publisher. - Check every captured page or story for content you did not intend to show, such as cookie banners, chat widgets or a loading spinner. Fix these in the application or in the capture script, not by raising the threshold.
- Accept the baseline only after the captures look correct. Once accepted, later runs compare against it, and a changed image is a review item to inspect before you accept the new version.
To run the comparison locally against the files you just captured, use the lower-level commands reg-suit exposes. The run command combines synchronizing expected images, comparing, publishing and optional notification, so it is the right command for CI, while the individual steps help when you are debugging a single stage.
Best Value
Storage, keys and notifications
- Comparison keys. The key-generation plugin decides which expected set a run is compared against. The Git-hash plugin keys results by commit, which means a pull request is compared with the base it started from. Confirm the behaviour in the plugin’s own documentation for your branch model before relying on it.
- Publishing. The publisher stores snapshot images and the HTML report in external storage. The reg-suit examples use the S3 publishing plugin with a bucket name in the configuration and AWS credentials from the environment. Google Cloud Storage is also mentioned in the reg-suit project description, so check the publisher plugin list for your provider.
- Pull request notifications. Optional GitHub pull-request comments are provided through a GitHub app and a notifier plugin. Set up the app and the plugin before expecting comments; without them the run still completes and publishes results.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| reg-suit reports no images or compares nothing | The capture wrote to a different folder than actualDir |
Compare the output path in capture.js with actualDir and make them identical, including the trailing folder name |
| Every page is reported as new on every run | No baseline is being selected, usually because the comparison key changes on each run or the publish step failed | Check the key plugin’s output in the log, confirm the publisher credentials, and confirm the previous run’s publish succeeded |
| Screenshots are blank or show a loading state | Capture ran before Angular rendered, or a router fallback was missing so deep links returned an error page | Keep the app-root:not(:empty) wait, add an application readiness flag, and confirm the static server falls back to index.html |
| Results flicker between runs | Animations, carousels, live data, or fonts loading late | Keep the freeze CSS, wait for document.fonts.ready, mock or seed live data, and mask regions that are inherently dynamic |
| Same page differs between a laptop and CI | Different operating system font rendering or device scale | Generate baselines only in CI, and keep the viewport and deviceScaleFactor constant in the script |
| Puppeteer fails to launch in CI | Missing Chromium dependencies or a sandbox restriction in a container | Use an image with browser dependencies installed; in containers where the sandbox cannot start, pass args: ['--no-sandbox'] to puppeteer.launch and accept the reduced isolation |
Capture times out on networkidle0 |
The app keeps an open connection, such as a polling request or websocket | Use waitUntil: 'domcontentloaded' with an explicit selector or readiness flag instead |
| Publish step fails with an authorization error | Missing or wrong storage credentials in CI | Verify the secret names in the workflow and that the bucket permissions allow writes from the job |
| Storybook capture produces no files or errors on Angular | The capture add-on does not support your Angular or Storybook version | Check the add-on’s current compatibility before debugging the script; fall back to Puppeteer for those pages |
Performance, reliability and cost notes
- Keep the page count small per run. Each captured route adds browser navigation time and a stored image. Start with the pages that carry the most risk and expand from there.
- Reuse one browser. The example opens one browser and one page for all routes, which avoids repeated start-up cost. Open a new page per route only if you see state leaking between captures.
- Use full-page captures deliberately. Full-page images are taller and make the report harder to review, but they catch layout breaks at the bottom of long pages.
- Storage grows with every run. Each published run stores images and a report. Set a retention policy on the bucket or prune old results according to your storage provider’s lifecycle options.
- Verify versions. The reg-suit examples are not a version guarantee. Pin the reg-suit, Puppeteer and Angular versions in
package.json, and update them deliberately when you review a change.
Or skip the browser setup
Or skip the browser setup: the same visual check can start with a single HTTP request to https://api.screenshotneo.com/v1/shot, and the code samples below use that endpoint. Pass your access key and a publicly reachable URL, and the response body is the image. The full list of options is in the ScreenshotNeo documentation.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
// save the response body to a file
const fs = require('fs');
fs.writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
The Python and cURL samples save WebP output. If your reg-suit setup needs a different format in actualDir, check the format options in the documentation before you wire the output into the comparison folder. The API fetches the URL from its own side, so it needs a publicly reachable staging or production address rather than localhost.
Why teams use it alongside or instead of a local browser:
- Cookie banners, newsletter popups and chat widgets are removed before the shot is taken, and each of those steps can be turned off.
- Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and each response says which outcome it was.
- An MCP server lets AI agents such as Claude or Cursor take screenshots and PDFs through the take_screenshot, get_page_info and capture_pdf tools.
- 1,000 screenshots a month are free with no card, and paid plans start at $5 for 3,000 screenshots.
Create a free account at https://screenshotneo.com/account/sign-up/ to get 1,000 free screenshots a month with no card, then run the cURL call above against your staging URL.
Frequently Asked Questions
Can the ScreenshotNeo call capture a staging site that requires a login?
Yes, through the request options. You can send custom headers, cookies, a user agent or an Authorization header with the request, which covers many staging setups that sit behind a token. Check the documentation for the exact parameter names, because this guide’s examples only show the access key and URL.
Is the Puppeteer script still needed if I use the hosted API for some pages?
Yes, for pages that need your own multi-step state, such as a sequence of clicks in a local build that is not publicly reachable. The hosted API is a good fit for public staging or production URLs; the script keeps full control of the session and the local build.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

