Skip to content

Commit 86bab1b

Browse files
sfanahataShannon Anahata
andauthored
chore: add screenshot pipeline workflow (#17805)
## Summary Adds the GitHub Actions workflow file for the automated screenshot pipeline so it can be triggered via `workflow_dispatch`. This is a prerequisite for testing the pipeline end-to-end -- GitHub requires workflow files to exist on the default branch before they can be manually triggered. The pipeline scripts and full configuration live on `feature/playwright-screenshot-pipeline`. This PR only adds the workflow YAML. ## What it does - Adds `.github/workflows/screenshot-pipeline.yml` - Weekly schedule (Monday 6am UTC) + manual trigger - Supports `scope`, `dry_run`, and threshold inputs - No impact on existing workflows or build process --------- Co-authored-by: Shannon Anahata <shannonanahata@gmail.com>
1 parent e7fcc66 commit 86bab1b

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
name: Screenshot Pipeline
2+
3+
# Automated screenshot capture, diff, and replacement pipeline.
4+
# Detects stale screenshots and Arcade embeds, auto-replaces high-confidence
5+
# diffs, and creates Linear issues for items requiring manual review.
6+
7+
on:
8+
schedule:
9+
- cron: '0 6 * * 1' # Weekly on Monday at 6am UTC
10+
workflow_dispatch: # Manual trigger
11+
inputs:
12+
scope:
13+
description: 'Limit crawl to a specific directory (e.g., docs/product/issues)'
14+
required: false
15+
type: string
16+
dry_run:
17+
description: 'Dry run mode (no file changes, no PRs, no Linear issues)'
18+
required: false
19+
type: boolean
20+
default: false
21+
diff_threshold_low:
22+
description: 'Min diff % to consider changed (default: 0.01 = 1%)'
23+
required: false
24+
type: string
25+
default: '0.01'
26+
diff_threshold_high:
27+
description: 'Max diff % before flagging as suspicious (default: 0.50 = 50%)'
28+
required: false
29+
type: string
30+
default: '0.50'
31+
32+
jobs:
33+
screenshot-pipeline:
34+
runs-on: ubuntu-latest
35+
timeout-minutes: 60
36+
37+
steps:
38+
- name: Checkout repository
39+
uses: actions/checkout@v4
40+
with:
41+
fetch-depth: 0 # Full history for git log queries
42+
43+
- name: Setup Node.js
44+
uses: actions/setup-node@v4
45+
with:
46+
node-version: 20
47+
48+
- name: Install pipeline dependencies
49+
run: npm install
50+
working-directory: scripts/screenshot-pipeline
51+
52+
- name: Install Playwright browsers
53+
run: npx playwright install --with-deps chromium
54+
working-directory: scripts/screenshot-pipeline
55+
56+
- name: Restore auth state
57+
if: ${{ !inputs.dry_run }}
58+
run: |
59+
echo "$SENTRY_STORAGE_STATE" | base64 -d > /tmp/storageState.json
60+
env:
61+
SENTRY_STORAGE_STATE: ${{ secrets.SENTRY_STORAGE_STATE }}
62+
63+
- name: Validate scope input
64+
if: ${{ inputs.scope != '' }}
65+
run: |
66+
# Validate scope is a safe directory path (alphanumeric, hyphens, slashes, dots only)
67+
if ! echo "$PIPELINE_SCOPE" | grep -qE '^[a-zA-Z0-9/_.-]+$'; then
68+
echo "Error: Invalid scope input. Must be a directory path (e.g., docs/product/issues)"
69+
exit 1
70+
fi
71+
env:
72+
PIPELINE_SCOPE: ${{ inputs.scope }}
73+
74+
- name: Run inventory crawler
75+
run: |
76+
SCOPE_ARGS=()
77+
if [ -n "$PIPELINE_SCOPE" ]; then
78+
SCOPE_ARGS=(--scope "$PIPELINE_SCOPE")
79+
fi
80+
npx ts-node src/crawl-inventory.ts "${SCOPE_ARGS[@]}"
81+
working-directory: scripts/screenshot-pipeline
82+
env:
83+
UI_REFRESH_CUTOFF: '2025-06-01'
84+
PIPELINE_SCOPE: ${{ inputs.scope }}
85+
86+
- name: Run screenshot capture & diff
87+
run: |
88+
DRY_RUN_ARGS=()
89+
if [ "$PIPELINE_DRY_RUN" = "true" ]; then
90+
DRY_RUN_ARGS=(--dry-run)
91+
fi
92+
npx ts-node src/capture-and-diff.ts "${DRY_RUN_ARGS[@]}"
93+
working-directory: scripts/screenshot-pipeline
94+
env:
95+
SENTRY_STORAGE_STATE_PATH: ${{ inputs.dry_run && '' || '/tmp/storageState.json' }}
96+
SENTRY_ORG_SLUG: ${{ secrets.SENTRY_ORG_SLUG }}
97+
SENTRY_BASE_URL: ${{ secrets.SENTRY_BASE_URL }}
98+
DIFF_THRESHOLD_LOW: ${{ inputs.diff_threshold_low || '0.01' }}
99+
DIFF_THRESHOLD_HIGH: ${{ inputs.diff_threshold_high || '0.50' }}
100+
PIPELINE_DRY_RUN: ${{ inputs.dry_run }}
101+
102+
- name: Auto-replace and open PR
103+
if: ${{ !inputs.dry_run }}
104+
run: npx ts-node src/auto-replace.ts
105+
working-directory: scripts/screenshot-pipeline
106+
env:
107+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
108+
109+
- name: Create Linear issues
110+
if: ${{ !inputs.dry_run }}
111+
run: npx ts-node src/create-linear-issues.ts
112+
working-directory: scripts/screenshot-pipeline
113+
env:
114+
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
115+
LINEAR_TEAM_ID: ${{ vars.LINEAR_TEAM_ID }}
116+
SENTRY_BASE_URL: ${{ secrets.SENTRY_BASE_URL }}
117+
118+
- name: Upload pipeline artifacts
119+
uses: actions/upload-artifact@v4
120+
if: always()
121+
with:
122+
name: screenshot-pipeline-output
123+
path: scripts/screenshot-pipeline/output/
124+
retention-days: 30
125+
126+
- name: Write job summary
127+
if: always()
128+
run: |
129+
echo "## Screenshot Pipeline Results" >> $GITHUB_STEP_SUMMARY
130+
echo "" >> $GITHUB_STEP_SUMMARY
131+
132+
if [ -f scripts/screenshot-pipeline/output/inventory-manifest.json ]; then
133+
TOTAL=$(jq length scripts/screenshot-pipeline/output/inventory-manifest.json)
134+
STALE=$(jq '[.[] | select(.is_stale == true)] | length' scripts/screenshot-pipeline/output/inventory-manifest.json)
135+
echo "**Inventory:** ${TOTAL} total assets, ${STALE} stale" >> $GITHUB_STEP_SUMMARY
136+
fi
137+
138+
if [ -f scripts/screenshot-pipeline/output/diff-results.json ]; then
139+
CAPTURED=$(jq length scripts/screenshot-pipeline/output/diff-results.json)
140+
AUTO=$(jq '[.[] | select(.status == "auto_replace")] | length' scripts/screenshot-pipeline/output/diff-results.json)
141+
REVIEW=$(jq '[.[] | select(.status == "needs_review")] | length' scripts/screenshot-pipeline/output/diff-results.json)
142+
FAILED=$(jq '[.[] | select(.status == "capture_failed")] | length' scripts/screenshot-pipeline/output/diff-results.json)
143+
echo "**Captures:** ${CAPTURED} processed" >> $GITHUB_STEP_SUMMARY
144+
echo "- Auto-replace: ${AUTO}" >> $GITHUB_STEP_SUMMARY
145+
echo "- Needs review: ${REVIEW}" >> $GITHUB_STEP_SUMMARY
146+
echo "- Capture failed: ${FAILED}" >> $GITHUB_STEP_SUMMARY
147+
fi
148+
149+
if [ -f scripts/screenshot-pipeline/output/linear-issues.json ]; then
150+
CREATED=$(jq '[.[] | select(.isNew == true)] | length' scripts/screenshot-pipeline/output/linear-issues.json)
151+
echo "**Linear issues created:** ${CREATED}" >> $GITHUB_STEP_SUMMARY
152+
fi

0 commit comments

Comments
 (0)