Skip to content

Screenshot failure comment #98

Screenshot failure comment

Screenshot failure comment #98

# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
name: Screenshot failure comment
on:
workflow_run:
workflows:
- Playwright
types:
- completed
permissions:
actions: read
contents: write
pull-requests: write
jobs:
comment:
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.pull_requests[0].number != null
runs-on: ubuntu-latest
steps:
- name: Download screenshot artifacts
id: artifacts
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
GH_REPO: ${{ github.repository }}
run: |
if gh run download "$RUN_ID" --repo "$GH_REPO" --name screenshot-diff --dir screenshot-artifacts; then
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish screenshot report
if: steps.artifacts.outputs.found == 'true'
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
RUN_ID: ${{ github.event.workflow_run.id }}
REPORT_BRANCH: screenshot-reports
shell: bash
run: |
set -euo pipefail
if ! gh api "repos/$GH_REPO/git/ref/heads/$REPORT_BRANCH" >/dev/null 2>&1; then
default_branch="$(gh api "repos/$GH_REPO" --jq '.default_branch')"
default_sha="$(gh api "repos/$GH_REPO/git/ref/heads/$default_branch" --jq '.object.sha')"
gh api --method POST "repos/$GH_REPO/git/refs" \
-f ref="refs/heads/$REPORT_BRANCH" \
-f sha="$default_sha" >/dev/null
fi
upload_png() {
local file="$1"
local repo_path="$2"
if [[ "$(file --brief --mime-type "$file")" != "image/png" ]]; then
echo "Refusing to publish non-PNG file: $file" >&2
exit 1
fi
if (( $(stat -c '%s' "$file") > 5242880 )); then
echo "Refusing to publish PNG larger than 5 MiB: $file" >&2
exit 1
fi
local encoded
encoded="$(base64 -w 0 "$file")"
local existing_sha
existing_sha="$(gh api "repos/$GH_REPO/contents/$repo_path?ref=$REPORT_BRANCH" --jq '.sha' 2>/dev/null || true)"
if [[ -n "$existing_sha" ]]; then
jq -n \
--arg message "ci: update screenshot report for PR #$PR_NUMBER" \
--arg content "$encoded" \
--arg branch "$REPORT_BRANCH" \
--arg sha "$existing_sha" \
'{message: $message, content: $content, branch: $branch, sha: $sha}' |
gh api --method PUT "repos/$GH_REPO/contents/$repo_path" --input - >/dev/null
else
jq -n \
--arg message "ci: add screenshot report for PR #$PR_NUMBER" \
--arg content "$encoded" \
--arg branch "$REPORT_BRANCH" \
'{message: $message, content: $content, branch: $branch}' |
gh api --method PUT "repos/$GH_REPO/contents/$repo_path" --input - >/dev/null
fi
}
report_path="pr-$PR_NUMBER"
upload_png screenshot-artifacts/demo-screenshot-expected.png "$report_path/expected.png"
upload_png screenshot-artifacts/demo-screenshot-current.png "$report_path/generated.png"
has_diff=false
if [[ -f screenshot-artifacts/demo-screenshot-diff.png ]]; then
upload_png screenshot-artifacts/demo-screenshot-diff.png "$report_path/diff.png"
has_diff=true
fi
raw_base="https://raw.githubusercontent.com/$GH_REPO/$REPORT_BRANCH/$report_path"
run_url="https://github.com/$GH_REPO/actions/runs/$RUN_ID"
marker='<!-- readme-screenshot-report -->'
summary_line="Screenshot output differs from the committed README image."
if [[ -f screenshot-artifacts/demo-screenshot-report.json ]]; then
report_type="$(jq -r '.type' screenshot-artifacts/demo-screenshot-report.json)"
if [[ "$report_type" == "pixels" ]]; then
changed_pixels="$(jq -r '.changedPixels' screenshot-artifacts/demo-screenshot-report.json)"
total_pixels="$(jq -r '.totalPixels' screenshot-artifacts/demo-screenshot-report.json)"
ratio="$(jq -r '.ratio * 100' screenshot-artifacts/demo-screenshot-report.json)"
max_ratio="$(jq -r '.maxDiffRatio * 100' screenshot-artifacts/demo-screenshot-report.json)"
summary_line="$(printf '**Difference:** %.4f%% · **Allowed:** %.4f%% \\n%s of %s pixels differ.' "$ratio" "$max_ratio" "$changed_pixels" "$total_pixels")"
elif [[ "$report_type" == "size" ]]; then
committed_size="$(jq -r '"\(.committed.width)x\(.committed.height)"' screenshot-artifacts/demo-screenshot-report.json)"
generated_size="$(jq -r '"\(.generated.width)x\(.generated.height)"' screenshot-artifacts/demo-screenshot-report.json)"
summary_line="**Size changed:** $committed_size → $generated_size"
fi
fi
cat > screenshot-comment.md <<EOF
$marker
## README screenshot check failed
$summary_line
[View workflow run]($run_url)
### Visual diff
EOF
if [[ "$has_diff" == "true" ]]; then
cat >> screenshot-comment.md <<EOF
![Visual diff]($raw_base/diff.png?run=$RUN_ID)
EOF
else
cat >> screenshot-comment.md <<'EOF'
A pixel diff is not available because the image dimensions changed.
EOF
fi
cat >> screenshot-comment.md <<EOF
<details>
<summary>Expected screenshot</summary>
![Expected screenshot]($raw_base/expected.png?run=$RUN_ID)
</details>
<details>
<summary>Generated screenshot</summary>
![Generated screenshot]($raw_base/generated.png?run=$RUN_ID)
</details>
If the visual change is expected, run:
\`npm run screenshots:update\`
EOF
existing_comment_id="$(
gh api "repos/$GH_REPO/issues/$PR_NUMBER/comments" --paginate \
--jq '.[] | select(.body | contains("<!-- readme-screenshot-report -->")) | .id' |
head -n 1
)"
if [[ -n "$existing_comment_id" ]]; then
jq -n --rawfile body screenshot-comment.md '{body: $body}' |
gh api --method PATCH "repos/$GH_REPO/issues/comments/$existing_comment_id" --input - >/dev/null
else
gh pr comment "$PR_NUMBER" --repo "$GH_REPO" --body-file screenshot-comment.md
fi
cat >> "$GITHUB_STEP_SUMMARY" <<EOF
## README screenshot check failed
$summary_line
[Open pull request](https://github.com/$GH_REPO/pull/$PR_NUMBER) · [View workflow run]($run_url)
### Visual diff
EOF
if [[ "$has_diff" == "true" ]]; then
cat >> "$GITHUB_STEP_SUMMARY" <<EOF
![Visual diff]($raw_base/diff.png?run=$RUN_ID)
EOF
else
cat >> "$GITHUB_STEP_SUMMARY" <<'EOF'
A pixel diff is not available because the image dimensions changed.
EOF
fi
cat >> "$GITHUB_STEP_SUMMARY" <<EOF
<details>
<summary>Expected screenshot</summary>
![Expected screenshot]($raw_base/expected.png?run=$RUN_ID)
</details>
<details>
<summary>Generated screenshot</summary>
![Generated screenshot]($raw_base/generated.png?run=$RUN_ID)
</details>
EOF