Skip to the content.

logo

πŸ“Έ 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.

GitHub release License: MIT Docker Image Maintained


✨ 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.0.17
        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"
  }
]

πŸ”€ PR Automation

When create_pr: "true", the action will:

  1. Check out (or create) the branch specified in branch_name
  2. Capture all screenshots and write them to output_dir
  3. Commit any changed or new .png files with [skip ci] to avoid re-triggering workflows
  4. Force-push the branch
  5. 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_TOKEN passed via env for 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: networkidle0 produces the most complete captures but is the slowest. If a site has continuous background requests (analytics, websockets), use networkidle2 to 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: square and full-page capture are mutually exclusive. When square is 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:

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

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

  1. Merge your changes to main
  2. The release.yml workflow automatically determines the next version via GitVersion
  3. It builds and pushes the Docker image to GHCR tagged as vX.Y.Z and latest
  4. It opens a PR updating the image: tag in action.yml β€” merge it to complete the release

πŸ“„ License

MIT Β© Guilherme Branco Stracini


Made with ❀️ and β˜• β€” contributions welcome via issues and pull requests.