Skip to content

ci: release

ci: release #10

Workflow file for this run

name: Visual Regression
# Untrusted measure job. Runs the visual-regression Playwright suite against the
# committed baselines and, when snapshots differ, produces an artifact with the
# candidate (accepted-state) baselines plus diff images. This workflow runs with
# a read-only token and never comments or pushes. The companion workflows handle
# that with elevated permissions, isolated from PR-authored code:
# - "Visual Regression — Report" (workflow_run) posts the sticky comment.
# - "Visual Regression — Apply" (schedule) commits accepted baselines when a
# maintainer reacts 👍 to the sticky comment.
#
# Baselines are Linux/chromium PNGs regenerated here on the CI runner, so they
# always match the environment they're diffed in. Contributors do NOT commit
# baselines by hand (macOS PNGs would never match); the 👍-accept flow commits
# the correct Linux baselines for them.
on:
pull_request:
branches: [main]
paths:
- "packages/admin/**"
- "packages/core/**"
- "e2e/**"
- "playwright.config.ts"
- ".github/workflows/visual.yml"
- "pnpm-lock.yaml"
- "package.json"
permissions:
contents: read
jobs:
measure:
name: Measure
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run --filter emdash... build
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('pnpm-lock.yaml') }}
- run: pnpm exec playwright install --with-deps chromium
if: steps.playwright-cache.outputs.cache-hit != 'true'
- name: Run visual regression
id: run
env:
EMDASH_VISUAL: "1"
run: |
set +e
pnpm exec playwright test visual-regression --reporter=line
code=$?
set -e
echo "exit=$code" >> "$GITHUB_OUTPUT"
echo "Playwright exited with $code"
# Always record PR metadata so the Report workflow can upsert *or* clear
# the sticky comment. Candidate baselines + diff images are added only
# when the suite actually diffed (exit != 0 AND regeneration produced
# snapshots) -- an infra failure exits non-zero but yields no candidates,
# so we don't mislabel it as a visual change.
- name: Collect result
id: collect
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
RUN_EXIT: ${{ steps.run.outputs.exit }}
run: |
mkdir -p visual-out
echo "$PR_NUMBER" > visual-out/pr-number
echo "$HEAD_SHA" > visual-out/head-sha
if [ "$RUN_EXIT" = "0" ]; then
echo "false" > visual-out/has-drift
echo "has_drift=false" >> "$GITHUB_OUTPUT"
echo "No visual drift."
exit 0
fi
# Copy the diff/actual/expected triptychs Playwright writes on failure.
mkdir -p visual-out/diff
if [ -d test-results ]; then
find test-results -type f \
\( -name '*-diff.png' -o -name '*-actual.png' -o -name '*-expected.png' \) \
-exec cp {} visual-out/diff/ \;
fi
# Regenerate the accepted-state baselines (the candidates the Apply
# workflow will commit verbatim on 👍). These are Linux/chromium PNGs.
pnpm exec playwright test visual-regression --update-snapshots --reporter=line || true
mkdir -p visual-out/candidates
if [ -d e2e/tests/visual-regression.spec.ts-snapshots ]; then
cp -r e2e/tests/visual-regression.spec.ts-snapshots/. visual-out/candidates/
fi
if [ -z "$(ls -A visual-out/candidates 2>/dev/null)" ]; then
# Non-zero exit but nothing regenerated -> infra failure, not drift.
echo "false" > visual-out/has-drift
echo "has_drift=false" >> "$GITHUB_OUTPUT"
echo "::warning::Visual suite failed without producing candidate baselines (likely an infra failure, not a UI diff)."
else
echo "true" > visual-out/has-drift
echo "has_drift=true" >> "$GITHUB_OUTPUT"
echo "Visual drift detected — candidates and diff images staged for the Report workflow."
fi
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: visual-regression
path: visual-out/
retention-days: 7
if-no-files-found: error
# Make the check red so a UI PR gets a visible signal. This does not, by
# itself, block merge -- keep it out of required checks until the false-
# diff rate is near zero, then promote it.
- name: Fail on drift
if: steps.collect.outputs.has_drift == 'true'
run: |
echo "::error::Visual snapshots changed. Review the diff in the sticky comment; a maintainer 👍 on that comment accepts the new baselines."
exit 1