Visual Screenshots #83
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Visual Screenshots | |
| on: | |
| deployment_status: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| screenshot: | |
| # Only run when Vercel preview deployment succeeds | |
| if: | | |
| github.event.deployment_status.state == 'success' && | |
| startsWith(github.event.deployment.environment, 'Preview') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Find PR for this deployment | |
| id: pr | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| script: | | |
| const ref = context.payload.deployment.ref; | |
| const { data: prs } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| head: `${context.repo.owner}:${ref}`, | |
| state: 'open', | |
| }); | |
| if (prs.length === 0) { | |
| core.info(`No open PR found for ref: ${ref}`); | |
| return ''; | |
| } | |
| core.info(`Found PR #${prs[0].number}`); | |
| return String(prs[0].number); | |
| - name: Skip if not a PR deployment | |
| if: steps.pr.outputs.result == '' | |
| run: | | |
| echo "Not a PR deployment — skipping screenshot capture." | |
| exit 0 | |
| - uses: actions/checkout@v4 | |
| if: steps.pr.outputs.result != '' | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node | |
| if: steps.pr.outputs.result != '' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| if: steps.pr.outputs.result != '' | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Install Playwright Chromium | |
| if: steps.pr.outputs.result != '' | |
| working-directory: frontend | |
| run: npx playwright install chromium --with-deps | |
| - name: Capture screenshots | |
| if: steps.pr.outputs.result != '' | |
| working-directory: frontend | |
| env: | |
| BASE_URL: ${{ github.event.deployment_status.target_url }} | |
| run: npx playwright test tests/visual-screenshots.spec.ts --project=chromium --reporter=list | |
| - name: Push screenshots to screenshots branch | |
| if: steps.pr.outputs.result != '' | |
| env: | |
| PR_NUMBER: ${{ steps.pr.outputs.result }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Ensure orphan screenshots branch exists on remote | |
| if git ls-remote --exit-code origin screenshots; then | |
| git fetch origin screenshots | |
| git checkout screenshots | |
| else | |
| git checkout --orphan screenshots | |
| git rm -rf . --quiet | |
| git commit --allow-empty -m "chore: init screenshots branch" | |
| git push origin screenshots | |
| fi | |
| # Copy screenshots into pr-{number}/ folder | |
| mkdir -p "pr-${PR_NUMBER}" | |
| cp frontend/test-screenshots/*.png "pr-${PR_NUMBER}/" | |
| git add "pr-${PR_NUMBER}/" | |
| git diff --cached --quiet && echo "No screenshot changes" && exit 0 | |
| git commit -m "screenshots: PR #${PR_NUMBER} — $(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| git push origin screenshots | |
| - name: Post or update PR comment | |
| if: steps.pr.outputs.result != '' | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ steps.pr.outputs.result }} | |
| PREVIEW_URL: ${{ github.event.deployment_status.target_url }} | |
| with: | |
| script: | | |
| const pr = parseInt(process.env.PR_NUMBER); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const base = `https://raw.githubusercontent.com/${owner}/${repo}/screenshots/pr-${pr}`; | |
| const url = process.env.PREVIEW_URL; | |
| const pages = [ | |
| { key: 'landing', label: 'Landing — Hero' }, | |
| { key: 'showcase', label: 'Screenshot Showcase' }, | |
| { key: 'architecture', label: 'Architecture Section' }, | |
| { key: 'dashboard', label: 'Dashboard' }, | |
| ]; | |
| const rows = pages | |
| .map(p => `| **${p.label}** |  |`) | |
| .join('\n'); | |
| const body = [ | |
| '## 📸 Visual Screenshots', | |
| '', | |
| `> Preview: ${url}`, | |
| '', | |
| '| Page | Screenshot |', | |
| '|------|-----------|', | |
| rows, | |
| '', | |
| `*Updated ${new Date().toUTCString()}*`, | |
| ].join('\n'); | |
| // Replace previous bot comment if one exists | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner, repo, issue_number: pr, | |
| }); | |
| const existing = comments.find( | |
| c => c.user.type === 'Bot' && c.body.includes('📸 Visual Screenshots') | |
| ); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, repo, comment_id: existing.id, body, | |
| }); | |
| core.info(`Updated comment ${existing.id}`); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: pr, body, | |
| }); | |
| core.info('Created new screenshot comment'); | |
| } |