chore: remove upstream Codespaces workflow #9
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: Storybook | |
| on: | |
| pull_request: | |
| merge_group: | |
| push: | |
| branches: | |
| - master | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | |
| # Cancel in-progress runs on PRs when new commits are pushed | |
| # SHA verification ensures we don't commit stale snapshots if runs aren't cancelled in time | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| # Job to decide if we should run storybook ci | |
| # See https://github.com/dorny/paths-filter#conditional-execution for more details | |
| changes: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| name: Determine need to run storybook checks | |
| if: github.event_name != 'merge_group' | |
| # Set job outputs to values from filter step | |
| outputs: | |
| frontend: ${{ steps.filter.outputs.frontend || 'true' }} | |
| mode: ${{ steps.detect.outputs.mode }} | |
| steps: | |
| - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 | |
| id: filter | |
| if: github.event_name != 'push' # Run all tests on master push | |
| with: | |
| filters: | | |
| frontend: | |
| - 'frontend/**' | |
| - 'products/**/*.{ts,tsx}' | |
| - 'products/**/frontend/**' | |
| - 'common/{esbuilder,mosaic,storybook,tailwind}/**' | |
| - 'ee/frontend/**' | |
| - '.storybook/**' | |
| - 'package.json' | |
| - '.github/workflows/ci-storybook.yml' | |
| - 'playwright.config.ts' | |
| - name: Detect snapshot mode | |
| id: detect | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then | |
| echo "mode=check" >> $GITHUB_OUTPUT | |
| echo "Fork detected - running in CHECK mode (no commits allowed)" | |
| elif [ -n "${{ github.event.pull_request.number }}" ] && gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels --jq '.[].name' | grep -q '^update-snapshots$'; then | |
| echo "mode=update" >> $GITHUB_OUTPUT | |
| echo "::notice::🔄 Running in UPDATE mode - 'update-snapshots' label detected" | |
| else | |
| AUTHOR="${{ github.actor }}" | |
| echo "Workflow triggered by: $AUTHOR" | |
| # Trusted bots that are allowed to run in update mode | |
| # Add bot usernames here to allow snapshot auto-updates on their PRs | |
| TRUSTED_BOTS=("mendral-app[bot]" "cursor[bot]") | |
| IS_TRUSTED=false | |
| for bot in "${TRUSTED_BOTS[@]}"; do | |
| if [[ "$AUTHOR" == "$bot" ]]; then | |
| IS_TRUSTED=true | |
| break | |
| fi | |
| done | |
| if [[ "$IS_TRUSTED" == "true" ]]; then | |
| echo "mode=update" >> $GITHUB_OUTPUT | |
| echo "::notice::🔄 Trusted bot '$AUTHOR' - running in UPDATE mode" | |
| else | |
| echo "mode=check" >> $GITHUB_OUTPUT | |
| echo "::notice::🔍 Running in CHECK mode - add 'update-snapshots' label to enable snapshot updates" | |
| fi | |
| fi | |
| build-storybook: | |
| name: Build Storybook | |
| runs-on: depot-ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 | |
| - name: Fix node-gyp permissions | |
| run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: pnpm | |
| cache-dependency-path: | | |
| pnpm-lock.yaml | |
| .github/workflows/ci-storybook.yml | |
| - name: Install dependencies | |
| run: pnpm --filter=@posthog/storybook... install --frozen-lockfile | |
| - name: Restore webpack build cache | |
| uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 | |
| with: | |
| path: common/storybook/node_modules/.cache/ | |
| key: ${{ runner.os }}-webpack-storybook-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: ${{ runner.os }}-webpack-storybook- | |
| - name: Build Storybook | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=32768 | |
| run: | | |
| bin/turbo --filter=@posthog/storybook prepare | |
| pnpm --filter=@posthog/storybook build --test | |
| - name: Save webpack build cache | |
| if: github.ref == 'refs/heads/master' | |
| uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 | |
| with: | |
| path: common/storybook/node_modules/.cache/ | |
| key: ${{ runner.os }}-webpack-storybook-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Upload Storybook build artifact | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: storybook-build | |
| path: common/storybook/dist | |
| retention-days: 1 | |
| visual-regression: | |
| name: Visual regression tests - ${{ matrix.browser }} (${{ matrix.shard }}/${{ matrix.shard_count }}) | |
| runs-on: ubuntu-latest | |
| needs: [changes, build-storybook] | |
| if: needs.changes.outputs.frontend == 'true' | |
| timeout-minutes: 60 | |
| container: | |
| image: mcr.microsoft.com/playwright:v1.45.0 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Shard counts and worker config (maxWorkers in common/storybook/package.json) | |
| # are tuned for runner CPU — consult #team-devex before changing | |
| include: | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 1 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 2 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 3 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 4 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 5 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 6 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 7 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 8 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 9 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 10 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 11 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 12 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 13 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 14 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 15 | |
| - browser: chromium | |
| shard_count: 16 | |
| shard: 16 | |
| - browser: webkit | |
| shard_count: 4 | |
| shard: 1 | |
| - browser: webkit | |
| shard_count: 4 | |
| shard: 2 | |
| - browser: webkit | |
| shard_count: 4 | |
| shard: 3 | |
| - browser: webkit | |
| shard_count: 4 | |
| shard: 4 | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=16384 | |
| OPT_OUT_CAPTURE: 1 | |
| JEST_JUNIT_SUITE_NAME: '{filepath}' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 | |
| - name: Fix node-gyp permissions | |
| run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: pnpm | |
| cache-dependency-path: | | |
| pnpm-lock.yaml | |
| .github/workflows/ci-storybook.yml | |
| - name: Install package.json dependencies with pnpm | |
| run: pnpm --filter=@posthog/storybook... install --frozen-lockfile | |
| - name: Install CI utilities with pnpm | |
| run: pnpm install http-server wait-on -g | |
| - name: Download Storybook build artifact | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: storybook-build | |
| path: common/storybook/dist | |
| - name: Serve Storybook in the background | |
| run: | | |
| retries=5 | |
| max_timeout=30 | |
| pnpm exec http-server common/storybook/dist --port 6006 --silent & | |
| server_pid=$! | |
| echo "Started http-server with PID: $server_pid" | |
| # Give the server a moment to start | |
| sleep 2 | |
| while [ $retries -gt 0 ]; do | |
| echo "Checking if Storybook is available (retries left: $retries, timeout: ${max_timeout}s)..." | |
| if pnpm wait-on http://127.0.0.1:6006 --timeout $max_timeout; then | |
| echo "✅ Storybook is available at http://127.0.0.1:6006" | |
| break | |
| fi | |
| retries=$((retries-1)) | |
| if [ $retries -gt 0 ]; then | |
| echo "⚠️ Failed to connect to Storybook, retrying... ($retries retries left)" | |
| # Check if server is still running | |
| if ! kill -0 $server_pid 2>/dev/null; then | |
| echo "❌ http-server process is no longer running, restarting it..." | |
| pnpm exec http-server common/storybook/dist --port 6006 --silent & | |
| server_pid=$! | |
| echo "Restarted http-server with PID: $server_pid" | |
| sleep 2 | |
| fi | |
| fi | |
| done | |
| if [ $retries -eq 0 ]; then | |
| echo "❌ Failed to serve Storybook after all retries" | |
| # Try to get some diagnostic information | |
| echo "Checking port 6006 status:" | |
| netstat -tuln | grep 6006 || echo "Port 6006 is not in use" | |
| echo "Checking http-server process:" | |
| ps aux | grep http-server || echo "No http-server process found" | |
| echo "Checking Storybook dist directory:" | |
| ls -la common/storybook/dist || echo "Storybook dist directory not found" | |
| exit 1 | |
| fi | |
| - name: Run @storybook/test-runner | |
| id: test-runner | |
| shell: bash | |
| env: | |
| HOME: /root | |
| STORYBOOK_SKIP_TAGS: 'test-skip,test-skip-${{ matrix.browser }}' | |
| run: | | |
| set +e | |
| MAX_VERIFY_ATTEMPTS=3 | |
| EXIT_CODE=1 | |
| for attempt in $(seq 1 $MAX_VERIFY_ATTEMPTS); do | |
| echo "Attempt $attempt/$MAX_VERIFY_ATTEMPTS: Running @storybook/test-runner (verify)" | |
| set -o pipefail | |
| pnpm --filter=@posthog/storybook test:visual:ci:verify --browsers ${{ matrix.browser }} --shard ${{ matrix.shard }}/${{ matrix.shard_count }} 2>&1 | tee /tmp/test-output-${{ matrix.browser }}-${{ matrix.shard }}-attempt${attempt}.log | |
| EXIT_CODE=$? | |
| set +o pipefail | |
| if [ $EXIT_CODE -eq 0 ]; then | |
| if [ $attempt -gt 1 ]; then | |
| echo "Attempt $attempt passed - previous failure was flaky" | |
| fi | |
| break | |
| fi | |
| # Missing snapshots will never pass on retry — skip straight to update | |
| if grep -qi "was not written" /tmp/test-output-${{ matrix.browser }}-${{ matrix.shard }}-attempt${attempt}.log 2>/dev/null; then | |
| echo "Missing snapshot detected - skipping remaining verify attempts" | |
| break | |
| fi | |
| echo "Attempt $attempt failed (exit code $EXIT_CODE)" | |
| done | |
| # All verify attempts failed - try snapshot update if applicable | |
| if [ $EXIT_CODE -ne 0 ]; then | |
| LAST_LOG="/tmp/test-output-${{ matrix.browser }}-${{ matrix.shard }}-attempt${attempt}.log" | |
| if [ "${{ needs.changes.outputs.mode }}" == "update" ] && grep -Eqi "(snapshot.*(failed|was not written)|Expected image to)" "$LAST_LOG" 2>/dev/null; then | |
| echo "Snapshot failure confirmed - updating snapshots" | |
| set -o pipefail | |
| pnpm --filter=@posthog/storybook test:visual:ci:update --browsers ${{ matrix.browser }} --shard ${{ matrix.shard }}/${{ matrix.shard_count }} 2>&1 | tee /tmp/test-output-${{ matrix.browser }}-${{ matrix.shard }}-update.log | |
| EXIT_CODE=$? | |
| set +o pipefail | |
| fi | |
| if [ $EXIT_CODE -ne 0 ]; then | |
| echo "Test runner failed after verify and update attempts" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Archive failure screenshots | |
| if: failure() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: failure-screenshots-${{ matrix.browser }}-${{ matrix.shard }} | |
| path: frontend/__snapshots__/__failures__/ | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: junit-results-storybook-${{ matrix.browser }}-${{ matrix.shard }} | |
| path: common/storybook/junit.xml | |
| if-no-files-found: ignore | |
| - name: Configure global git diff log | |
| run: git config --global --add safe.directory '*' | |
| # Create git patch for aggregation (handles A/M/D including binary files) | |
| # Only run in UPDATE mode - CHECK mode doesn't update snapshots | |
| # Run even if tests failed, as long as snapshots may have been updated | |
| - name: Create snapshot patch | |
| id: create-patch | |
| if: needs.changes.outputs.mode == 'update' && (success() || failure()) && github.event.pull_request.head.repo.full_name == github.repository | |
| run: | | |
| # Verify we're in a git repository before running git commands | |
| if ! git rev-parse --git-dir > /dev/null 2>&1; then | |
| echo "Not in a git repository, skipping patch creation" | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Check if there are any changes (modified or untracked) in the snapshot directory | |
| # Use git status --porcelain to detect both modifications and new untracked files | |
| if [ -z "$(git status --porcelain frontend/__snapshots__/)" ]; then | |
| echo "No snapshot changes" | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Snapshot changes detected:" | |
| git status --short frontend/__snapshots__/ | |
| # Stage untracked files so git diff can include them in the patch | |
| git add -N frontend/__snapshots__/ | |
| # Create binary-safe patch file | |
| mkdir -p /tmp/patches | |
| git diff --binary --full-index frontend/__snapshots__/ > /tmp/patches/shard-${{ matrix.browser }}-${{ matrix.shard }}.patch | |
| echo "Created patch file" | |
| echo "has-changes=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Upload snapshot patch | |
| if: steps.create-patch.outputs.has-changes == 'true' && (success() || failure()) | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: snapshot-patch-${{ matrix.browser }}-${{ matrix.shard }} | |
| path: /tmp/patches/shard-${{ matrix.browser }}-${{ matrix.shard }}.patch | |
| retention-days: 1 | |
| if-no-files-found: ignore | |
| # Verify changed story files are stable by re-running them multiple times. | |
| # Catches flaky snapshots before they land on master. | |
| flake-verification: | |
| name: Storybook flake verification | |
| runs-on: ubuntu-latest | |
| needs: [changes, build-storybook] | |
| if: needs.changes.outputs.frontend == 'true' && github.event_name == 'pull_request' | |
| timeout-minutes: 30 | |
| container: | |
| image: mcr.microsoft.com/playwright:v1.45.0 | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=16384 | |
| OPT_OUT_CAPTURE: 1 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| repository: ${{ github.event.pull_request.head.repo.full_name }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 | |
| - name: Fix node-gyp permissions | |
| run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: pnpm | |
| cache-dependency-path: | | |
| pnpm-lock.yaml | |
| .github/workflows/ci-storybook.yml | |
| - name: Install dependencies | |
| run: pnpm --filter=@posthog/storybook... install --frozen-lockfile | |
| - name: Install CI utilities | |
| run: pnpm install http-server wait-on -g | |
| - name: Download Storybook build artifact | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: storybook-build | |
| path: common/storybook/dist | |
| - name: Serve Storybook | |
| run: | | |
| pnpm exec http-server common/storybook/dist --port 6006 --silent & | |
| sleep 2 | |
| pnpm wait-on http://127.0.0.1:6006 --timeout 30 | |
| - name: Verify changed stories are stable | |
| id: flake-verify | |
| env: | |
| HOME: /root | |
| run: | | |
| git config --global --add safe.directory '*' | |
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | |
| git fetch --no-tags --prune --depth=50 origin "$BASE_SHA" | |
| .github/scripts/verify-storybook-new-stories.sh "$BASE_SHA" 3 | |
| - name: Upload flake verification screenshots | |
| if: failure() && steps.flake-verify.outcome == 'failure' | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: flake-verification-diffs | |
| path: | | |
| frontend/__snapshots__/__diff_output__/ | |
| frontend/__snapshots__/__failures__/ | |
| if-no-files-found: ignore | |
| retention-days: 5 | |
| # Post a helpful comment when snapshot tests fail in check mode (no label) | |
| snapshot-failure-comment: | |
| name: Post snapshot failure guidance | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: [visual-regression, changes] | |
| if: >- | |
| always() | |
| && needs.visual-regression.result == 'failure' | |
| && needs.changes.outputs.mode == 'check' | |
| && github.event_name == 'pull_request' | |
| && github.event.pull_request.head.repo.full_name == github.repository | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const marker = '<!-- storybook-snapshot-hint -->'; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find(c => c.body.includes(marker)); | |
| const body = `${marker} | |
| ### Storybook visual regression tests failed | |
| **If your changes intentionally update UI snapshots:** add the \`update-snapshots\` label, then re-run the workflow. Snapshots will be auto-committed. | |
| **If you didn't change any UI:** this is likely a flaky snapshot — wait for a fix to land on master.`.replace(/^ /gm, ''); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| # Clean up the hint comment when tests pass | |
| snapshot-success-cleanup: | |
| name: Clean up snapshot hint | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: [visual-regression, changes] | |
| if: >- | |
| always() | |
| && needs.visual-regression.result == 'success' | |
| && github.event_name == 'pull_request' | |
| && github.event.pull_request.head.repo.full_name == github.repository | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const marker = '<!-- storybook-snapshot-hint -->'; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find(c => c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| }); | |
| } | |
| # Job to collate the status of the matrix jobs for requiring passing status | |
| # Must depend on handle-snapshots to prevent auto-merge before commits complete | |
| visual_regression_tests: | |
| needs: [visual-regression, handle-snapshots] | |
| name: Visual regression tests pass | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: always() | |
| steps: | |
| - name: Check matrix outcome | |
| run: | | |
| # Check visual-regression matrix result | |
| if [[ "${{ needs.visual-regression.result }}" != "success" && "${{ needs.visual-regression.result }}" != "skipped" ]]; then | |
| echo "One or more jobs in the visual-regression test matrix failed." | |
| exit 1 | |
| fi | |
| # Check handle-snapshots result (OK if skipped, but fail if it failed) | |
| if [[ "${{ needs.handle-snapshots.result }}" == "failure" ]]; then | |
| echo "Snapshot commit job failed." | |
| exit 1 | |
| fi | |
| echo "All jobs passed or were skipped successfully." | |
| handle-snapshots: | |
| name: Handle snapshot changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [visual-regression, changes] | |
| if: always() && needs.changes.outputs.frontend == 'true' && github.event.pull_request.head.repo.full_name == github.repository | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| # Use GitHub app token so Actions run after commiting updated snapshots | |
| - name: Get app token | |
| id: app-token | |
| uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3.0.0 | |
| with: | |
| app_id: ${{ secrets.GH_APP_POSTHOG_TESTS_APP_ID }} | |
| private_key: ${{ secrets.GH_APP_POSTHOG_TESTS_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} | |
| token: ${{ steps.app-token.outputs.token || github.token }} | |
| fetch-depth: 1 | |
| - name: Download snapshot patches | |
| id: download-patches | |
| continue-on-error: true | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| pattern: snapshot-patch-* | |
| path: /tmp/snapshot-patches/ | |
| merge-multiple: true | |
| - name: Check for snapshot changes | |
| id: check-snapshots | |
| run: | | |
| # Check if patches were downloaded and have content | |
| if [ "${{ steps.download-patches.outcome }}" == "failure" ] || [ ! -d /tmp/snapshot-patches/ ]; then | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| echo "No snapshot patches found" | |
| exit 0 | |
| fi | |
| # Check if any patch files have content (>0 bytes) | |
| PATCHES=$(find /tmp/snapshot-patches -name "*.patch" -type f -size +0c) | |
| if [ -z "$PATCHES" ]; then | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| echo "Patch files empty - no snapshot changes" | |
| else | |
| echo "has-changes=true" >> $GITHUB_OUTPUT | |
| echo "Snapshot changes detected in patches" | |
| fi | |
| # Apply patches BEFORE VR so it hashes the final state | |
| # Uses --index so changes are staged for the downstream commit action | |
| - name: Apply snapshot patches | |
| if: steps.check-snapshots.outputs.has-changes == 'true' | |
| run: | | |
| for patch in $(find /tmp/snapshot-patches -name "*.patch" -type f -size +0c | sort); do | |
| echo "Applying $patch" | |
| git apply --index "$patch" | |
| done | |
| # Visual Review: submit snapshots after patches are applied | |
| - name: Install VR CLI deps | |
| run: cd products/visual_review/cli && npm ci | |
| - name: Submit to Visual Review | |
| continue-on-error: true | |
| env: | |
| VR_TOKEN: ${{ secrets.VR_API_TOKEN }} | |
| VR_BRANCH: ${{ github.event.pull_request.head.ref }} | |
| VR_COMMIT: ${{ github.event.pull_request.head.sha }} | |
| VR_PR: ${{ github.event.pull_request.number }} | |
| VR_AUTO_APPROVE: ${{ needs.changes.outputs.mode == 'update' && '--auto-approve' || '' }} | |
| run: | | |
| npx tsx products/visual_review/cli/src/index.ts submit \ | |
| --dir frontend/__snapshots__/ \ | |
| --type storybook \ | |
| --baseline frontend/snapshots.yml \ | |
| $VR_AUTO_APPROVE \ | |
| --token "$VR_TOKEN" \ | |
| --branch "$VR_BRANCH" \ | |
| --commit "$VR_COMMIT" \ | |
| --pr "$VR_PR" | |
| - name: Check baseline changes | |
| id: check-baseline | |
| run: | | |
| if git diff --quiet frontend/snapshots.yml; then | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "has-changes=true" >> $GITHUB_OUTPUT | |
| git add frontend/snapshots.yml | |
| fi | |
| # UPDATE mode: commit PNGs + baseline together | |
| # Patches already applied above — commit-snapshots will re-apply (idempotent) | |
| - name: Commit snapshot changes | |
| if: (steps.check-snapshots.outputs.has-changes == 'true' || steps.check-baseline.outputs.has-changes == 'true') && needs.changes.outputs.mode == 'update' | |
| uses: ./.github/actions/commit-snapshots | |
| with: | |
| workflow-type: storybook | |
| patch-path: /tmp/snapshot-patches/ | |
| snapshot-path: frontend/__snapshots__/ | |
| extra-files: frontend/snapshots.yml | |
| commit-message: 'test(storybook): update UI snapshots' | |
| pr-number: ${{ github.event.pull_request.number }} | |
| repository: ${{ github.repository }} | |
| commit-sha: ${{ github.event.pull_request.head.sha }} | |
| branch-name: ${{ github.event.pull_request.head.ref }} | |
| github-token: ${{ steps.app-token.outputs.token || github.token }} | |
| calculate-running-time: | |
| name: Calculate running time | |
| needs: [visual-regression, changes] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: # Run on pull requests to PostHog/posthog + on PostHog/posthog outside of PRs - but never on forks or Dependabot (no secrets access) | |
| always() && github.actor != 'dependabot[bot]' && | |
| needs.changes.outputs.frontend == 'true' && ( | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'PostHog/posthog') || | |
| (github.event_name != 'pull_request' && github.repository == 'PostHog/posthog')) | |
| steps: | |
| - name: Capture running time to PostHog | |
| uses: PostHog/posthog-github-action@eea1405eeb2d1d2259f0ee0b44d7b152869e6840 # v1.1.1 | |
| with: | |
| posthog-token: ${{ secrets.POSTHOG_API_TOKEN }} | |
| event: 'posthog-ci-running-time' | |
| capture-run-duration: true | |
| capture-job-durations: true | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| status-job: 'Visual regression tests pass' |