Back to blog
TutorialsCI/CDAutomation

Visual checks for every pull request with GitHub Actions

Capture your preview deployment on each PR and post the screenshots as a comment, so reviewers see the change without clicking through.

Ruby Le
Ruby Le
Project Manager
· Apr 22, 2026· 2 min read

Code review is good at catching logic bugs and bad at catching "the button is now white on white". A screenshot in the PR fixes that. This workflow captures a few pages of your preview deployment and comments the images on the pull request.

Prerequisites

  • A preview URL per PR (Vercel, Netlify, Cloudflare Pages and most platforms provide one).
  • SNAPOPA_API_KEY stored as a repository secret.

The workflow

name: Visual preview

on:
  pull_request:

jobs:
  screenshots:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Capture pages
        id: capture
        env:
          SNAPOPA_API_KEY: ${{ secrets.SNAPOPA_API_KEY }}
          PREVIEW_URL: https://pr-${{ github.event.number }}.preview.example.com
        run: |
          body="### Visual preview"$'\n'
          for path in / /pricing /docs; do
            url=$(curl -s -X POST https://api.snapopa.com/capture \
              -H "Authorization: Bearer $SNAPOPA_API_KEY" \
              -H "Content-Type: application/json" \
              -d "{\"url\":\"$PREVIEW_URL$path\",\"format\":\"webp\",\"isFullPage\":true}" \
              | jq -r '.data.fileUrl')
            body+=$'\n'"**$path**"$'\n'"![$path]($url)"$'\n'
          done
          echo "body<<EOF" >> "$GITHUB_OUTPUT"
          echo "$body" >> "$GITHUB_OUTPUT"
          echo "EOF" >> "$GITHUB_OUTPUT"

      - name: Comment on PR
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          message: ${{ steps.capture.outputs.body }}

The sticky comment action updates the same comment on each push instead of adding a new one.

Making it robust

  • Wait for the deploy. Trigger on your platform's deployment-status event, or add a step that polls the preview URL until it returns 200.
  • Mobile too. Add a second loop with "viewport":{"width":390,"height":844}.
  • Keep the images. Capture files expire with cdnTtl. For long-lived PRs, raise cdnTtl or use permalinks.
Try Snapopa

Reading about screenshots? Try the API.

100 free tokens on signup. No credit card. One request is all it takes.

$ curl https://api.snapopa.com/capture \
    -H "Authorization: Bearer sk_•••" \
    -d '{"url":"https://stripe.com"}'

✓ 200 OK
{ "data": { "fileUrl": "https://cdn.snapopa.com/…" } }