Tools: GitHub Actions Screenshots: Auto-Capture Deploy Evidence (5-Minute Setup)

Tools: GitHub Actions Screenshots: Auto-Capture Deploy Evidence (5-Minute Setup)

GitHub Actions Screenshots: Auto-Capture Deploy Evidence (5-Minute Setup)

The Problem: Manual Screenshot Chaos

The Solution: Automated Screenshots in GitHub Actions

The Screenshot Capture Script

Real-World Example: Compliance Evidence

Environment Setup

Why This Beats Manual Screenshots

Next Steps 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. Right now, your team probably: The evidence trail is broken. Here's a GitHub Actions workflow that: Create capture-screenshots.js: For regulated industries (fintech, healthcare, insurance), this workflow creates an audit trail: Add to GitHub Secrets: For teams shipping frequently (10+ deploys/week), automated screenshots save money and create compliance evidence simultaneously. 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. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ 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 -weight: 500;">npm run build -weight: 500;">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 }); 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 -weight: 500;">npm run build -weight: 500;">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 }); 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 -weight: 500;">npm run build -weight: 500;">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 }); 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); 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); 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); // 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`); } // 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`); } // 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`); } PAGEBOLT_API_KEY: pf_live_xxx STAGING_URL: https://staging.example.com PAGEBOLT_API_KEY: pf_live_xxx STAGING_URL: https://staging.example.com PAGEBOLT_API_KEY: pf_live_xxx STAGING_URL: https://staging.example.com - 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 - Runs after deployment - Takes a screenshot of your app - Attaches it to the PR - Sends a notification to your team - 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 - 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 - Copy the workflow YAML to .github/workflows/deploy-screenshots.yml - Copy capture-screenshots.js to your repo root - Add PAGEBOLT_API_KEY to GitHub Secrets - Deploy → see screenshots auto-attached to PR - Auditors see proof of what shipped āœ“