Skip to content

Publish Screenshots #109

Publish Screenshots

Publish Screenshots #109

---
name: Publish Screenshots
on:
workflow_run:
workflows: ["CI"]
types:
- completed
permissions:
actions: read
contents: write
pull-requests: write
statuses: write
jobs:
publish:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Set Commit Status (In Progress)
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
with:
script: |
const {
GITHUB_REPOSITORY,
GITHUB_RUN_ID,
GITHUB_SERVER_URL,
WORKFLOW_HEAD_SHA,
} = process.env;
const targetUrl = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: WORKFLOW_HEAD_SHA,
state: 'pending',
target_url: targetUrl,
description: 'Screenshots publishing in progress',
context: 'publish-screenshots',
});
- name: Download Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
run-id: ${{ github.event.workflow_run.id }}
path: run-screenshots
pattern: tray-screenshots-*
- name: Prepare Matrix Screenshot Directories
run: |
mkdir -p prepared
shopt -s nullglob
for artifact_dir in run-screenshots/tray-screenshots-*; do
[ -d "${artifact_dir}" ] || continue
matrix_name="${artifact_dir##*/tray-screenshots-}"
mkdir -p "prepared/${matrix_name}"
cp -R "${artifact_dir}/." "prepared/${matrix_name}/"
done
if [ -z "$(find prepared -mindepth 1 -print -quit)" ]; then
echo "No screenshots were downloaded from CI artifacts."
exit 1
fi
echo "Prepared screenshot files:"
find prepared -type f | sort
- name: Determine Context
id: context
env:
WORKFLOW_EVENT: ${{ github.event.workflow_run.event }}
WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
run: |
event_name="${WORKFLOW_EVENT}"
head_branch="${WORKFLOW_HEAD_BRANCH}"
pr_number="$(jq -r '.workflow_run.pull_requests[0].number // empty' "${GITHUB_EVENT_PATH}")"
if [ -n "${pr_number}" ] && ! [[ "${pr_number}" =~ ^[0-9]+$ ]]; then
echo "Invalid pr_number value: ${pr_number}"
exit 1
fi
is_pr=false
if [ "${event_name}" = "pull_request" ] && [ -n "${pr_number}" ]; then
is_pr=true
fi
is_master_push=false
if [ "${event_name}" = "push" ] && [ "${head_branch}" = "master" ]; then
is_master_push=true
fi
{
echo "event_name=${event_name}"
echo "head_branch=${head_branch}"
echo "is_pr=${is_pr}"
echo "pr_number=${pr_number}"
echo "is_master_push=${is_master_push}"
} >> "${GITHUB_OUTPUT}"
- name: Checkout Screenshots Branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: screenshots
path: screenshots-repo
- name: Sync Screenshot Content
id: sync
if: steps.context.outputs.is_master_push == 'true' || steps.context.outputs.is_pr == 'true'
env:
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
run: |
target_dir=""
if [ "${{ steps.context.outputs.is_master_push }}" = "true" ]; then
target_dir="screenshots-repo/baseline"
elif [ "${{ steps.context.outputs.is_pr }}" = "true" ]; then
if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then
echo "Invalid PR number: ${PR_NUMBER}"
exit 1
fi
target_dir="screenshots-repo/pull-requests/PR-${PR_NUMBER}"
else
echo "Unsupported workflow context for screenshot sync."
exit 1
fi
mkdir -p "${target_dir}"
# Mirror the prepared set and delete files removed from CI output.
rsync -a --delete prepared/ "${target_dir}/"
echo "target_dir=${target_dir}" >> "${GITHUB_OUTPUT}"
- name: Build PR Screenshot Comparison Comment
if: steps.context.outputs.is_pr == 'true'
env:
BASELINE_ROOT: screenshots-repo/baseline
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
PR_ROOT: prepared
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
WORKFLOW_RUN_NUMBER: ${{ github.event.workflow_run.run_number }}
WORKFLOW_RUN_URL: ${{ github.event.workflow_run.html_url }}
run: |
raw_base="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/screenshots"
baseline_root="${BASELINE_ROOT}"
pr_root="${PR_ROOT}"
cache_buster="${WORKFLOW_HEAD_SHA}"
run_date="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
{
echo "| | |"
echo "| --- | --- |"
printf "| **Last Updated** | %s |\n" "${run_date}"
printf "| **Source Run** | [CI Run #%s](%s) |\n" "${WORKFLOW_RUN_NUMBER}" "${WORKFLOW_RUN_URL}"
printf "| **Commit** | \`%s\` |\n" "${WORKFLOW_HEAD_SHA}"
echo
echo "## Screenshot Comparison"
echo
printf "PR #%s screenshots vs \`screenshots\` baseline.\n\n" "${PR_NUMBER}"
} > pr-comment.md
tmp_baseline="$(mktemp)"
tmp_pr="$(mktemp)"
tmp_matrices="$(mktemp)"
if [ -d "${baseline_root}" ]; then
find "${baseline_root}" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' >> "${tmp_matrices}"
fi
if [ -d "${pr_root}" ]; then
find "${pr_root}" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' >> "${tmp_matrices}"
fi
if [ ! -s "${tmp_matrices}" ]; then
echo "No matrix screenshots were found in baseline or PR artifacts." >> pr-comment.md
rm -f "${tmp_baseline}" "${tmp_pr}" "${tmp_matrices}"
exit 0
fi
while IFS= read -r matrix; do
[ -n "${matrix}" ] || continue
baseline_matrix="${baseline_root}/${matrix}"
pr_matrix="${pr_root}/${matrix}"
: > "${tmp_baseline}"
: > "${tmp_pr}"
if [ -d "${baseline_matrix}" ]; then
(
cd "${baseline_matrix}"
find . -type f | sed 's#^\./##' | LC_ALL=C sort
) > "${tmp_baseline}"
fi
if [ -d "${pr_matrix}" ]; then
(
cd "${pr_matrix}"
find . -type f | sed 's#^\./##' | LC_ALL=C sort
) > "${tmp_pr}"
fi
{
printf "### Matrix: \`%s\`\n\n" "${matrix}"
echo "| Image | Baseline | PR |"
echo "| --- | --- | --- |"
} >> pr-comment.md
tmp_all="$(mktemp)"
cat "${tmp_baseline}" "${tmp_pr}" | LC_ALL=C sort -u > "${tmp_all}"
if [ ! -s "${tmp_all}" ]; then
echo "| _(none)_ | | |" >> pr-comment.md
echo >> pr-comment.md
rm -f "${tmp_all}"
continue
fi
while IFS= read -r rel_file; do
[ -n "${rel_file}" ] || continue
baseline_cell=""
if grep -Fxq "${rel_file}" "${tmp_baseline}"; then
img_src="${raw_base}/baseline/${matrix}/${rel_file}?v=${cache_buster}"
baseline_cell="<img src=\"${img_src}\" width=\"320\" />"
fi
pr_cell=""
if grep -Fxq "${rel_file}" "${tmp_pr}"; then
img_src="${raw_base}/pull-requests/PR-${PR_NUMBER}/${matrix}/${rel_file}?v=${cache_buster}"
pr_cell="<img src=\"${img_src}\" width=\"320\" />"
fi
printf "| \`%s\` | %s | %s |\n" "${rel_file}" "${baseline_cell}" "${pr_cell}" >> pr-comment.md
done < "${tmp_all}"
echo >> pr-comment.md
rm -f "${tmp_all}"
done < <(LC_ALL=C sort -u "${tmp_matrices}")
rm -f "${tmp_baseline}" "${tmp_pr}" "${tmp_matrices}"
echo "Generated pr-comment.md"
- name: Commit and Push Screenshot Changes
id: push
if: steps.context.outputs.is_master_push == 'true' || steps.context.outputs.is_pr == 'true'
env:
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
cd screenshots-repo
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
commit_message=""
if [ "${{ steps.context.outputs.is_master_push }}" = "true" ]; then
git add -A baseline
commit_message="chore: update screenshots (${WORKFLOW_HEAD_SHA})"
elif [ "${{ steps.context.outputs.is_pr }}" = "true" ]; then
if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then
echo "Invalid PR number: ${PR_NUMBER}"
exit 1
fi
git add -A "pull-requests/PR-${PR_NUMBER}"
commit_message="chore: update PR-${PR_NUMBER} screenshots (${WORKFLOW_HEAD_SHA})"
else
echo "Unsupported workflow context for commit/push."
exit 1
fi
if git diff --cached --quiet; then
echo "has_changes=false" >> "${GITHUB_OUTPUT}"
exit 0
fi
git commit -m "${commit_message}"
git push origin screenshots
echo "has_changes=true" >> "${GITHUB_OUTPUT}"
- name: Post PR Comparison Comment
if: steps.context.outputs.is_pr == 'true'
uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue: ${{ steps.context.outputs.pr_number }}
message-path: pr-comment.md
message-id: screenshot-comparison
- name: Set Commit Status (Complete)
if: always()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
JOB_STATUS: ${{ job.status }}
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
with:
script: |
const success = process.env.JOB_STATUS === 'success';
const {
GITHUB_REPOSITORY,
GITHUB_RUN_ID,
GITHUB_SERVER_URL,
WORKFLOW_HEAD_SHA,
} = process.env;
const targetUrl = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: WORKFLOW_HEAD_SHA,
state: success ? 'success' : 'failure',
target_url: targetUrl,
description: success ? 'Screenshots published successfully' : 'Screenshots publishing failed',
context: 'publish-screenshots',
});