GitHub Actions Screenshots: Auto-Capture Deploy Evidence (5-Minute Setup)
In regulated industries, "prove what shipped" isn't optional ā it's compliance. When you deploy to production, auditors want evidence: screenshots of the UI state, configuration pages, deployment logs, environment settings.
Most teams solve this manually: engineers take screenshots, attach them to Slack, lose them when the thread archives. There's a better way: automatically capture screenshots in GitHub Actions and attach them to every PR.
Here's how to set it up in 5 minutes.
The Problem: Manual Screenshot Chaos
Right now, your team probably:
- Deploys to staging manually and takes screenshots
- Posts them to Slack (where they disappear in 90 days)
- Or writes them to a shared folder (that nobody actually accesses)
- Auditors ask "what did you ship?" ā panic ā search Slack archives
The evidence trail is broken.
The Solution: Automated Screenshots in GitHub Actions
Here's a GitHub Actions workflow that:
- Runs after deployment
- Takes a screenshot of your app
- Attaches it to the PR
- Sends a notification to your team
name: Deploy & Screenshot
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Staging
run: |
# Your deployment commands
npm run build
npm run deploy:staging
- name: Wait for Deploy
run: sleep 30
- name: Capture Screenshots
env:
PAGEBOLT_API_KEY: ${{ secrets.PAGEBOLT_API_KEY }}
run: |
node capture-screenshots.js
- name: Upload Screenshots to PR
uses: actions/upload-artifact@v3
with:
name: deploy-screenshots
path: screenshots/
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const screenshots = fs.readdirSync('screenshots/');
let comment = '## šø Deployment Screenshots\n\n';
screenshots.forEach(file => {
comment += `- [${file}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }})\n`;
});
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
The Screenshot Capture Script
Create capture-screenshots.js:
const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');
const PAGEBOLT_API_KEY = process.env.PAGEBOLT_API_KEY;
const STAGING_URL = process.env.STAGING_URL || 'https://staging.example.com';
async function captureScreenshots() {
const screenshots = [
{
name: 'homepage.png',
url: `${STAGING_URL}/`,
viewport: { width: 1280, height: 720 }
},
{
name: 'dashboard.png',
url: `${STAGING_URL}/dashboard`,
viewport: { width: 1280, height: 720 }
},
{
name: 'settings.png',
url: `${STAGING_URL}/settings`,
viewport: { width: 1280, height: 720 }
}
];
const dir = 'screenshots';
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
for (const screenshot of screenshots) {
try {
console.log(`Capturing ${screenshot.name}...`);
const response = await fetch('https://api.pagebolt.com/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${PAGEBOLT_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: screenshot.url,
viewport: screenshot.viewport,
format: 'png'
})
});
if (!response.ok) {
console.error(`Failed to capture ${screenshot.name}: ${response.statusText}`);
continue;
}
const buffer = await response.buffer();
fs.writeFileSync(path.join(dir, screenshot.name), buffer);
console.log(`ā Saved ${screenshot.name}`);
} catch (error) {
console.error(`Error capturing ${screenshot.name}:`, error.message);
}
}
console.log(`\nā All screenshots captured and saved to ${dir}/`);
}
captureScreenshots().catch(console.error);
Real-World Example: Compliance Evidence
For regulated industries (fintech, healthcare, insurance), this workflow creates an audit trail:
// capture-screenshots.js with compliance metadata
async function captureWithMetadata() {
const metadata = {
timestamp: new Date().toISOString(),
commit: process.env.GITHUB_SHA,
branch: process.env.GITHUB_REF,
environment: 'staging',
version: require('./package.json').version
};
const response = await fetch('https://api.pagebolt.com/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://staging.example.com/dashboard',
viewport: { width: 1280, height: 720 },
format: 'png',
// Metadata persists with the screenshot
metadata: metadata
})
});
const buffer = await response.buffer();
// Save with metadata in filename
const filename = `screenshot-${metadata.timestamp.replace(/[:.]/g, '-')}.png`;
fs.writeFileSync(path.join('screenshots', filename), buffer);
console.log(`ā Screenshot saved with compliance metadata`);
}
Environment Setup
Add to GitHub Secrets:
PAGEBOLT_API_KEY: pf_live_xxx
STAGING_URL: https://staging.example.com
Why This Beats Manual Screenshots
Manual approach:
- Engineer screenshots locally (5 min per deploy)
- Posts to Slack (lost after 90 days)
- Audit asks "what shipped?" ā dig through archives
- No timestamp, no commit metadata, no proof
Automated approach:
- Zero manual work per deploy
- Screenshots attached to PR (permanent record)
- Metadata (commit, timestamp, environment) embedded
- Audit asks "what shipped?" ā GitHub link + screenshot = instant proof
- Cost: $29-79/month vs. 5 minutes Ć $150/hr Ć weekly deploys = $1,560/month in engineer time
For teams shipping frequently (10+ deploys/week), automated screenshots save money and create compliance evidence simultaneously.
Next Steps
- Copy the workflow YAML to
.github/workflows/deploy-screenshots.yml - Copy
capture-screenshots.jsto your repo root - Add
PAGEBOLT_API_KEYto GitHub Secrets - Deploy ā see screenshots auto-attached to PR
- Auditors see proof of what shipped ā
Start free at pagebolt.dev/pricing ā 100 screenshots/month, no credit card required.
For regulated teams: Screenshot automation + GitHub PR history = SOC 2 / compliance-grade deployment evidence. No manual work. No lost screenshots. Just proof.
Top comments (0)