πΈ github-screenshot-action
Capture, monitor, and version website screenshots β automatically.
A reusable GitHub Action that takes screenshots of websites from a JSON list, with parallel execution, retry logic, cron scheduling, and automated PR creation.
β¨ Features
| Feature | Description |
|---|---|
| π JSON-driven | Define all your target sites in a simple JSON file β no code changes needed |
| β‘ Parallel execution | Configurable concurrency to capture multiple sites simultaneously |
| π Retry & timeout | Automatically retries failed captures with configurable limits |
| π Cron scheduling | Run on any schedule to monitor visual changes over time |
| πΏ Branch isolation | Screenshots are committed to a dedicated branch, keeping main clean |
| π Automated PRs | Optionally open a pull request automatically after each monitoring run |
| π³ Pre-built Docker image | No cold build β uses a pre-published image from GHCR for fast startup |
| π Chromium-based | Full Puppeteer + Chromium stack for accurate, real-browser rendering |
| β³ Wait strategies | Wait for full page load, network idle, or DOM ready before capturing |
| π₯ Square mode | Optionally clip output to a square for thumbnails or social previews |
π Quick Start
1. Create your sites file
Add a sites.json file to your repository:
[
{ "name": "homepage", "url": "https://example.com" },
{ "name": "dashboard", "url": "https://app.example.com/dashboard" },
{ "name": "pricing", "url": "https://example.com/pricing" }
]
Each entry requires a url and a name. The name becomes the screenshot filename (e.g. homepage.png).
2. Create your workflow
name: Website monitoring
on:
schedule:
- cron: "0 */6 * * *" # every 6 hours
workflow_dispatch:
jobs:
monitor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run screenshot monitoring
uses: guibranco/github-screenshot-action@v2.1.10
with:
json_file: "sites.json"
output_dir: "screenshots/"
concurrency: "5"
retries: "2"
timeout_ms: "20000"
create_pr: "true"
branch_name: "monitor/screenshots"
wait_until: "networkidle0"
square: "false"
viewport_width: "1280"
env:
GITHUB_TOKEN: $
βοΈ Inputs
| Input | Required | Default | Description |
|---|---|---|---|
json_file |
β | screenshots.json |
Path to the JSON file listing sites to capture |
output_dir |
β | screenshots |
Directory where .png files will be saved |
concurrency |
β | 3 |
Number of screenshots to take in parallel |
retries |
β | 2 |
How many times to retry a failed screenshot |
timeout_ms |
β | 30000 |
Page load timeout per site in milliseconds |
create_pr |
β | false |
If true, opens a PR after committing screenshots |
branch_name |
β | monitor/screenshots |
Branch to commit screenshots to |
wait_until |
β | load |
Page load event to wait for before capturing. See Wait Strategies |
square |
β | false |
If true, clips the output to a square using viewport_width as the side length |
viewport_width |
β | 1280 |
Viewport width in pixels. Also controls the square size when square is true |
π JSON File Format
[
{
"name": "landing-page",
"url": "https://example.com"
},
{
"name": "login",
"url": "https://example.com/login"
}
]
nameβ used as the output filename (name.png). Use lowercase, hyphen-separated values for clean filenames.urlβ full URL to capture. If the protocol is omitted,https://is prepended automatically.
π PR Automation
When create_pr: "true", the action will:
- Check out (or create) the branch specified in
branch_name - Capture all screenshots and write them to
output_dir - Commit any changed or new
.pngfiles with[skip ci]to avoid re-triggering workflows - Force-push the branch
- Open a pull request against
mainβ or skip silently if a PR already exists
This gives you a clean, reviewable diff of visual changes over time.
Note: The workflow needs
GITHUB_TOKENpassed viaenvfor PR creation and branch push to work.
env:
GITHUB_TOKEN: $
β³ Wait Strategies
The wait_until input controls when Puppeteer considers the page ready to capture. Choose based on how dynamic the target site is:
| Value | Waits until⦠| Best for |
|---|---|---|
domcontentloaded |
HTML is parsed, no assets waited on | Simple static pages |
load |
All resources loaded (default) | Most standard websites |
networkidle2 |
No more than 2 requests in flight for 500ms | Pages with background polling |
networkidle0 |
Zero network requests for 500ms | SPAs and lazy-loaded content |
Tip:
networkidle0produces the most complete captures but is the slowest. If a site has continuous background requests (analytics, websockets), usenetworkidle2to avoid hitting the timeout.
π₯ Square Mode
When square: "true", the action sets a square viewport and clips the output to viewport_width Γ viewport_width pixels. Full-page scrolling is disabled in this mode.
square: "true"
viewport_width: "1280" # produces a 1280Γ1280 PNG
This is useful for generating consistent thumbnails, social preview images, or monitoring dashboards where uniform dimensions are required.
Note:
squareand full-page capture are mutually exclusive. Whensquareis enabled, only the top portion of the page visible within the square viewport is captured.
ποΈ Scheduling Examples
# Every 6 hours
- cron: "0 */6 * * *"
# Once a day at midnight UTC
- cron: "0 0 * * *"
# Every Monday at 8am UTC
- cron: "0 8 * * 1"
# Every hour during business hours (9β17) on weekdays
- cron: "0 9-17 * * 1-5"
ποΈ Architecture
sites.json
β
βΌ
parser.ts βββΊ loadItems()
β
βΌ
screenshot.ts βββΊ Puppeteer + pLimit (parallel)
β β
β setViewport(width, height)
β goto(url, { waitUntil })
β screenshot({ fullPage | clip })
β
βΌ
output_dir/*.png
β
βΌ
git.ts βββΊ checkout branch
β git add -f / commit
β git push --force
β GitHub API PR (optional)
βΌ
Pull Request / Branch
π³ Docker Image
This action uses a pre-built Docker image published to GitHub Container Registry, so there is no build step at runtime.
ghcr.io/guibranco/github-screenshot-action:<version>
The image includes:
node:24-slimbase- Chromium and all required system dependencies
- Pre-compiled TypeScript output in
dist/
New images are published automatically on every release via the release.yml workflow.
π Permissions
Your workflow needs the following permissions for full functionality:
permissions:
contents: write # to push the screenshot branch
pull-requests: write # to open PRs
If you are using a classic GITHUB_TOKEN without an explicit permissions block, make sure your repositoryβs Actions settings allow workflows to create pull requests.
π οΈ Development
Prerequisites
- Node.js 24+
- npm
Setup
git clone https://github.com/guibranco/github-screenshot-action.git
cd github-screenshot-action
npm install
Build
npm run build
Output is written to dist/.
Project Structure
βββ src/
β βββ main.ts # Entry point β orchestrates the full run
β βββ parser.ts # Reads and validates sites.json
β βββ screenshot.ts # Puppeteer screenshot logic with concurrency
β βββ git.ts # Git operations: commit, branch, PR creation
β βββ logger.ts # Emoji-prefixed console logger
βββ Dockerfile # Pre-built image definition
βββ entrypoint.sh # Docker entrypoint
βββ action.yml # Action metadata
βββ sites.json # Example sites file
Publishing a new version
- Merge your changes to
main - The
release.ymlworkflow automatically determines the next version via GitVersion - It builds and pushes the Docker image to GHCR tagged as
vX.Y.Z - It opens a PR updating the
image:tag inaction.ymlβ merge it to complete the release - Merging that PR tags the squash commit on
mainasvX.Y.Z, creates the GitHub release and promotes the image tolatest(ifaction.ymlonmainalready pinsvX.Y.Z, steps 4β5 happen in one run with no PR)
The tag is created on main rather than on the release branch so GitVersion can see it: main only accepts squash merges, so a tag placed on the branch would never be reachable from main and the version would never move past it.
π License
MIT Β© Guilherme Branco Stracini
Made with β€οΈ and β β contributions welcome via issues and pull requests.