Skip to content

[Graphite MQ] Draft PR GROUP:spec_762ef7 (PRs 393, 403) #485

[Graphite MQ] Draft PR GROUP:spec_762ef7 (PRs 393, 403)

[Graphite MQ] Draft PR GROUP:spec_762ef7 (PRs 393, 403) #485

Workflow file for this run

name: Storybook Snapshots
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
concurrency:
group: storybook-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
storybook-tests:
name: Storybook Snapshot Tests
runs-on: [self-hosted, linux]
timeout-minutes: 25
permissions:
contents: read
deployments: write
issues: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/setup
with:
darkmatter-cachix-auth-token: ${{ secrets.DARKMATTER_CACHIX_AUTH_TOKEN }}
- name: Resolve secrets
env:
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
run: |
SECRETS_FILE="ops/secrets/secrets.yaml"
if [ ! -f "$SECRETS_FILE" ]; then
echo "::error::SOPS secrets file not found at $SECRETS_FILE"
exit 1
fi
CLOUDFLARE_API_TOKEN=$(sops -d --extract '["CLOUDFLARE_API_TOKEN"]' "$SECRETS_FILE")
CLOUDFLARE_ACCOUNT_ID=$(sops -d --extract '["CLOUDFLARE_ACCOUNT_ID"]' "$SECRETS_FILE")
echo "::add-mask::$CLOUDFLARE_API_TOKEN"
echo "::add-mask::$CLOUDFLARE_ACCOUNT_ID"
{
echo "CLOUDFLARE_API_TOKEN=$CLOUDFLARE_API_TOKEN"
echo "CLOUDFLARE_ACCOUNT_ID=$CLOUDFLARE_ACCOUNT_ID"
} >> "$GITHUB_ENV"
- name: Check Playwright browser availability
working-directory: apps/native
run: |
bunx playwright install --dry-run chromium
DEBUG=pw:browser bun --eval '
const { chromium } = await import("playwright");
const browser = await chromium.launch({
args: [
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu-sandbox",
"--disable-gpu",
"--no-zygote",
],
});
const page = await browser.newPage();
await page.setContent("<h1>ok</h1>");
console.log(await page.textContent("h1"));
await browser.close();
'
- name: Build Storybook
working-directory: apps/native
run: bun run build-storybook
- name: Create Cloudflare Pages project (if needed)
env:
CLOUDFLARE_API_TOKEN: ${{ env.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ env.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler pages project create nixmac-storybook --production-branch=main 2>/dev/null || true
- name: Deploy to Cloudflare Pages
id: deploy
env:
CLOUDFLARE_API_TOKEN: ${{ env.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ env.CLOUDFLARE_ACCOUNT_ID }}
BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
set -euo pipefail
output=$(bunx wrangler pages deploy apps/native/storybook-static \
--project-name=nixmac-storybook \
--commit-dirty=true \
--branch="$BRANCH" 2>&1 | tee /dev/stderr)
url=$(echo "$output" | grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev' | head -n1 || true)
if [ -z "$url" ]; then
echo "::error::Cloudflare Pages deploy output did not include a detectable preview URL"
exit 1
fi
echo "Storybook preview: $url"
echo "deployment-url=$url" >> "$GITHUB_OUTPUT"
# Story-level digest of new/changed/removed stories vs the PR base,
# rendered into the sticky comment so reviewers can see what changed.
- name: Build story-change digest
if: github.event_name == 'pull_request'
continue-on-error: true
working-directory: apps/native
env:
BASE_REF: origin/${{ github.base_ref }}
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
run: node scripts/build-storybook-digest.mjs
- name: Publish Storybook deployment link
if: steps.deploy.outputs.deployment-url != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
REF: ${{ github.event.pull_request.head.sha || github.sha }}
REPO: ${{ github.repository }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
deployment_id=$(gh api --method POST "repos/${REPO}/deployments" --jq '.id' --input - <<EOF
{
"ref": "${REF}",
"environment": "storybook-preview",
"description": "Storybook preview",
"auto_merge": false,
"required_contexts": [],
"transient_environment": true,
"production_environment": false
}
EOF
)
gh api --method POST "repos/${REPO}/deployments/${deployment_id}/statuses" --input - <<EOF >/dev/null
{
"state": "success",
"environment_url": "${DEPLOY_URL}",
"log_url": "${RUN_URL}",
"description": "Storybook preview is ready",
"auto_inactive": false
}
EOF
- name: Comment Storybook preview URL on PR
if: always() && !cancelled() && github.event_name == 'pull_request' && steps.deploy.outputs.deployment-url != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
REPO: ${{ github.repository }}
SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
MARKER='<!-- nixmac-storybook-preview -->'
DIGEST=""
if [ -f apps/native/test-results/storybook-digest.md ]; then
DIGEST=$(cat apps/native/test-results/storybook-digest.md)
fi
BODY=$(cat <<EOF
${MARKER}
### 🎨 Storybook preview
[Open Storybook preview](${DEPLOY_URL})
Updated for ${SHA}
${DIGEST}
EOF
)
# Find existing sticky comment (if any) and update; otherwise create.
EXISTING_ID=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--paginate --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" \
| head -n1 || true)
if [ -n "${EXISTING_ID}" ]; then
gh api --method PATCH "repos/${REPO}/issues/comments/${EXISTING_ID}" \
-f body="${BODY}" >/dev/null
echo "Updated sticky comment ${EXISTING_ID}"
else
gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "${BODY}"
echo "Created sticky comment"
fi
- name: Run Storybook snapshot tests
id: snapshot_tests
working-directory: apps/native
run: bun run test:storybook:ci
# ---- Failed-story screenshots (PR only, only when snapshots failed) ----
# Resolve the failed stories (recorded by the runner) into Storybook IDs +
# deep links, and emit the Creevey skip regex used to scope capture.
- name: Resolve failed stories
id: resolve_failures
if: failure() && steps.snapshot_tests.conclusion == 'failure' && github.event_name == 'pull_request' && steps.deploy.outputs.deployment-url != ''
continue-on-error: true
working-directory: apps/native
env:
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
run: |
node scripts/resolve-failed-stories.mjs
count=$(node -e 'const r=require("./test-results/failed-stories-resolved.json"); process.stdout.write(String(r.length))')
echo "count=$count" >> "$GITHUB_OUTPUT"
# Rebuild Storybook with the skip regex baked in, serve it locally, and let
# Creevey (Playwright, no Docker) screenshot only the failed stories.
- name: Capture failed-story screenshots (Creevey)
if: ${{ failure() && steps.resolve_failures.outcome == 'success' && steps.resolve_failures.outputs.count != '0' }}
continue-on-error: true
working-directory: apps/native
run: |
export VITE_CREEVEY_SKIP_REGEX="$(cat test-results/creevey-skip-regex.txt)"
bun run build-storybook
python3 -m http.server 6100 --directory storybook-static >/tmp/creevey-storybook.log 2>&1 &
SB_PID=$!
trap 'kill $SB_PID 2>/dev/null || true' EXIT
for _ in $(seq 1 30); do
curl -sf http://localhost:6100/index.json >/dev/null && break
sleep 1
done
CREEVEY_STORYBOOK_URL="http://localhost:6100" bunx creevey test --no-docker || true
node scripts/harvest-creevey-shots.mjs
# Host the screenshots on Cloudflare Pages so the PR comment can embed them.
- name: Deploy failed-story screenshots
id: deploy_shots
if: ${{ failure() && steps.resolve_failures.outcome == 'success' && steps.resolve_failures.outputs.count != '0' }}
continue-on-error: true
env:
CLOUDFLARE_API_TOKEN: ${{ env.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ env.CLOUDFLARE_ACCOUNT_ID }}
BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
set -euo pipefail
bunx wrangler pages project create nixmac-storybook-shots --production-branch=main 2>/dev/null || true
output=$(bunx wrangler pages deploy apps/native/test-results/shots \
--project-name=nixmac-storybook-shots \
--commit-dirty=true \
--branch="$BRANCH" 2>&1 | tee /dev/stderr)
url=$(echo "$output" | grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev' | head -n1 || true)
echo "shots-url=$url" >> "$GITHUB_OUTPUT"
# Re-render the sticky comment to include the failed-snapshot gallery.
- name: Update PR comment with failed snapshots
if: ${{ failure() && steps.deploy_shots.outcome == 'success' && steps.deploy_shots.outputs.shots-url != '' }}
continue-on-error: true
working-directory: apps/native
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
MARKER: "<!-- nixmac-storybook-preview -->"
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
SHOTS_BASE_URL: ${{ steps.deploy_shots.outputs.shots-url }}
run: |
set -euo pipefail
BODY="$(node scripts/build-failed-comment.mjs)"
EXISTING_ID=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--paginate --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" \
| head -n1 || true)
if [ -n "${EXISTING_ID}" ]; then
gh api --method PATCH "repos/${REPO}/issues/comments/${EXISTING_ID}" \
-f body="${BODY}" >/dev/null
echo "Updated sticky comment ${EXISTING_ID}"
else
gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "${BODY}"
echo "Created sticky comment"
fi
- name: Check for snapshot staleness
if: always() && !cancelled()
working-directory: apps/native
run: |
if git diff --name-only | grep -q '__snapshots__'; then
echo "::error::Storybook snapshots are out of date. Run 'bun run test:update-snapshots' locally and commit the changes."
git diff --stat -- '*/__snapshots__/*'
exit 1
fi
- name: Upload snapshot diffs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: storybook-snapshot-diffs
path: |
apps/native/src/**/__snapshots__/__diff_output__/
apps/native/test-results/
retention-days: 7
- name: Post summary
if: always()
run: |
echo "### Storybook Snapshot Tests" >> "$GITHUB_STEP_SUMMARY"
DEPLOY_URL="${{ steps.deploy.outputs.deployment-url }}"
if [ -n "$DEPLOY_URL" ]; then
echo "[Open Storybook preview]($DEPLOY_URL)" >> "$GITHUB_STEP_SUMMARY"
fi
if [ "${{ job.status }}" == "success" ]; then
echo "All Storybook snapshot tests passed" >> "$GITHUB_STEP_SUMMARY"
else
echo "Storybook snapshot tests failed" >> "$GITHUB_STEP_SUMMARY"
echo "Check the 'storybook-snapshot-diffs' artifact for visual diffs." >> "$GITHUB_STEP_SUMMARY"
fi