Image publishing never succeeded: GHCR rejects the capitalized repository slug, and the arm64 jobs failed on the macOS runners, which have no Docker buildx. Resolve the namespace in lowercase and build amd64 only until an arm64 runner with buildx exists; the manifest check expects one digest. The Evo deploy ran on every push to main regardless of the result of Product CI, so red commits went live. Trigger it from the completed ci workflow instead and deploy the head it verified; manual dispatch stays. The Playwright report publish failed every nightly run because the S3 settings are not configured. The script now skips the gallery with a notice when they are missing instead of failing the job. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
288 lines
10 KiB
Bash
Executable File
288 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Publishing is optional: without the S3 settings the gallery is skipped and the job stays green.
|
|
for setting in AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY S3_BUCKET S3_ENDPOINT PLAYWRIGHT_PUBLIC_BASE_URL; do
|
|
if [[ -z "${!setting:-}" ]]; then
|
|
echo "::notice::Skipping the Playwright report publish: $setting is not configured."
|
|
exit 0
|
|
fi
|
|
done
|
|
: "${PLAYWRIGHT_RESULT:?PLAYWRIGHT_RESULT is required}"
|
|
: "${PLAYWRIGHT_RUN_ATTEMPT:?PLAYWRIGHT_RUN_ATTEMPT is required}"
|
|
: "${PLAYWRIGHT_RUN_ID:?PLAYWRIGHT_RUN_ID is required}"
|
|
: "${PLAYWRIGHT_RUN_NUMBER:?PLAYWRIGHT_RUN_NUMBER is required}"
|
|
: "${PLAYWRIGHT_RUN_URL:?PLAYWRIGHT_RUN_URL is required}"
|
|
: "${PLAYWRIGHT_SHA:?PLAYWRIGHT_SHA is required}"
|
|
: "${PLAYWRIGHT_EVENT:?PLAYWRIGHT_EVENT is required}"
|
|
: "${PLAYWRIGHT_BRANCH:?PLAYWRIGHT_BRANCH is required}"
|
|
|
|
report_dir="${PLAYWRIGHT_REPORT_DIR:-playwright-report}"
|
|
test_results_dir="${PLAYWRIGHT_TEST_RESULTS_DIR:-apps/web/test-results}"
|
|
publish_report="${PLAYWRIGHT_PUBLISH_REPORT:-true}"
|
|
|
|
if [[ "$publish_report" != "true" && "$publish_report" != "false" ]]; then
|
|
echo "PLAYWRIGHT_PUBLISH_REPORT must be true or false." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$publish_report" == "true" && ! -f "$report_dir/index.html" ]]; then
|
|
echo "::warning::Playwright did not produce an HTML report; publishing the gallery and dashboard entry without it."
|
|
publish_report="false"
|
|
fi
|
|
|
|
if [[ ! "$S3_BUCKET" =~ ^[a-zA-Z0-9.-]+$ ]]; then
|
|
echo "S3_BUCKET contains invalid characters." >&2
|
|
exit 1
|
|
fi
|
|
|
|
case "$S3_ENDPOINT" in
|
|
https://*) ;;
|
|
*) echo "S3_ENDPOINT must use HTTPS." >&2; exit 1 ;;
|
|
esac
|
|
|
|
case "$PLAYWRIGHT_PUBLIC_BASE_URL" in
|
|
https://*) ;;
|
|
*) echo "PLAYWRIGHT_PUBLIC_BASE_URL must use HTTPS." >&2; exit 1 ;;
|
|
esac
|
|
|
|
dashboard_dir="$GITHUB_WORKSPACE/.tmp/playwright-dashboard"
|
|
history_path="$dashboard_dir/history.json"
|
|
dashboard_path="$dashboard_dir/index.html"
|
|
gallery_dir="$dashboard_dir/screenshots"
|
|
history_error_path="$dashboard_dir/history-download-error.log"
|
|
baseline_manifest_path="$dashboard_dir/main-baseline-manifest.json"
|
|
baseline_error_path="$dashboard_dir/baseline-download-error.log"
|
|
existing_stable_baseline_path="$dashboard_dir/existing-main-baseline-manifest.json"
|
|
run_key="${PLAYWRIGHT_RUN_ID}-${PLAYWRIGHT_RUN_ATTEMPT}"
|
|
bucket_uri="s3://${S3_BUCKET}/playwright"
|
|
stable_baseline_uri="$bucket_uri/baselines/main/manifest.json"
|
|
public_base_url="${PLAYWRIGHT_PUBLIC_BASE_URL%/}"
|
|
report_url="$public_base_url/runs/$run_key/report/index.html"
|
|
screenshots_url="$public_base_url/runs/$run_key/screenshots/index.html"
|
|
pull_request_url=""
|
|
|
|
if [[ -n "${PLAYWRIGHT_PR_NUMBER:-}" ]]; then
|
|
if [[ ! "$PLAYWRIGHT_PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "PLAYWRIGHT_PR_NUMBER must be a positive integer." >&2
|
|
exit 1
|
|
fi
|
|
: "${PLAYWRIGHT_REPOSITORY_URL:?PLAYWRIGHT_REPOSITORY_URL is required for pull request runs}"
|
|
case "$PLAYWRIGHT_REPOSITORY_URL" in
|
|
https://*) ;;
|
|
*) echo "PLAYWRIGHT_REPOSITORY_URL must use HTTPS." >&2; exit 1 ;;
|
|
esac
|
|
pull_request_url="${PLAYWRIGHT_REPOSITORY_URL%/}/pull/$PLAYWRIGHT_PR_NUMBER"
|
|
fi
|
|
|
|
if [[ "$publish_report" == "false" ]]; then
|
|
report_url=""
|
|
fi
|
|
|
|
mkdir -p "$dashboard_dir"
|
|
if aws s3 cp \
|
|
"$bucket_uri/history.json" \
|
|
"$history_path" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
2>"$history_error_path"; then
|
|
echo "Downloaded existing Playwright history."
|
|
elif grep -Eq "404|NoSuchKey|Not Found" "$history_error_path"; then
|
|
echo "No existing Playwright history found; creating it."
|
|
else
|
|
cat "$history_error_path"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "${PLAYWRIGHT_PR_NUMBER:-}" ]]; then
|
|
# Prefer the dedicated latest-main baseline so comparison still works after
|
|
# that successful run ages out of the 100-entry dashboard history.
|
|
if aws s3 cp \
|
|
"$stable_baseline_uri" \
|
|
"$baseline_manifest_path" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
2>"$baseline_error_path"; then
|
|
echo "Downloaded the latest successful main screenshot baseline."
|
|
elif grep -Eq "404|NoSuchKey|Not Found" "$baseline_error_path"; then
|
|
if [[ -f "$history_path" ]]; then
|
|
baseline_run_key="$(jq -r '
|
|
[.[] | select(
|
|
.event == "push" and
|
|
.branch == "main" and
|
|
.result == "success" and
|
|
(.id | type == "string") and
|
|
(.attempt | type == "number")
|
|
)][0] |
|
|
if . then .id + "-" + (.attempt | tostring) else empty end
|
|
' "$history_path")"
|
|
if [[ -n "$baseline_run_key" ]]; then
|
|
if aws s3 cp \
|
|
"$bucket_uri/runs/$baseline_run_key/screenshots/manifest.json" \
|
|
"$baseline_manifest_path" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
2>"$baseline_error_path"; then
|
|
echo "Downloaded the latest successful main screenshot baseline from run history."
|
|
else
|
|
echo "::warning::The latest successful main run has no usable screenshot manifest; comparison labels will be unavailable."
|
|
fi
|
|
else
|
|
echo "::warning::No successful main run is available as a screenshot baseline."
|
|
fi
|
|
else
|
|
echo "::warning::No Playwright history is available for a screenshot baseline."
|
|
fi
|
|
else
|
|
echo "::warning::Could not download the latest successful main screenshot baseline; comparison labels will be unavailable."
|
|
cat "$baseline_error_path"
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "${PLAYWRIGHT_PR_NUMBER:-}" && "$PLAYWRIGHT_EVENT" == "push" && "$PLAYWRIGHT_BRANCH" == "main" && "$PLAYWRIGHT_RESULT" == "success" ]]; then
|
|
stable_baseline_missing="false"
|
|
stable_baseline_downloaded="false"
|
|
for attempt in 1 2 3; do
|
|
if aws s3 cp \
|
|
"$stable_baseline_uri" \
|
|
"$existing_stable_baseline_path" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
2>"$baseline_error_path"; then
|
|
stable_baseline_downloaded="true"
|
|
break
|
|
fi
|
|
if grep -Eq "404|NoSuchKey|Not Found" "$baseline_error_path"; then
|
|
stable_baseline_missing="true"
|
|
break
|
|
fi
|
|
if (( attempt < 3 )); then
|
|
sleep 2
|
|
fi
|
|
done
|
|
if [[ "$stable_baseline_downloaded" == "true" ]]; then
|
|
export PLAYWRIGHT_EXISTING_STABLE_BASELINE_PATH="$existing_stable_baseline_path"
|
|
echo "Downloaded the published main screenshot baseline for recency checks."
|
|
elif [[ "$stable_baseline_missing" == "true" ]]; then
|
|
echo "No published main screenshot baseline yet."
|
|
else
|
|
# History was downloaded successfully and is the publication-order source
|
|
# of truth. Treat an unreadable baseline as absent so the newest successful
|
|
# main run can establish it; an older run is still rejected by the history
|
|
# recency check emitted by the generator.
|
|
echo "::warning::Could not read the published main screenshot baseline after 3 attempts; treating it as absent."
|
|
cat "$baseline_error_path"
|
|
fi
|
|
fi
|
|
|
|
PLAYWRIGHT_REPORT_URL="$report_url" \
|
|
PLAYWRIGHT_SCREENSHOTS_URL="$screenshots_url" \
|
|
PLAYWRIGHT_DASHBOARD_URL="$public_base_url/index.html" \
|
|
PLAYWRIGHT_PR_URL="$pull_request_url" \
|
|
pnpm exec tsx \
|
|
packages/testkit/src/cli/generate-playwright-report-dashboard.ts \
|
|
"$history_path" \
|
|
"$dashboard_path" \
|
|
"$test_results_dir" \
|
|
"$gallery_dir" \
|
|
"$baseline_manifest_path"
|
|
|
|
if [[ "$publish_report" == "true" ]]; then
|
|
aws s3 sync \
|
|
"$report_dir/" \
|
|
"$bucket_uri/runs/$run_key/report/" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--cache-control "public,max-age=31536000,immutable"
|
|
fi
|
|
aws s3 sync \
|
|
"$gallery_dir/images/" \
|
|
"$bucket_uri/runs/$run_key/screenshots/images/" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--content-type "image/png" \
|
|
--no-guess-mime-type \
|
|
--cache-control "public,max-age=31536000,immutable"
|
|
aws s3 cp \
|
|
"$gallery_dir/index.html" \
|
|
"$bucket_uri/runs/$run_key/screenshots/index.html" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--content-type "text/html" \
|
|
--cache-control "public,max-age=31536000,immutable"
|
|
aws s3 cp \
|
|
"$gallery_dir/manifest.json" \
|
|
"$bucket_uri/runs/$run_key/screenshots/manifest.json" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--content-type "application/json" \
|
|
--cache-control "public,max-age=31536000,immutable"
|
|
if [[ -z "${PLAYWRIGHT_PR_NUMBER:-}" && "$PLAYWRIGHT_EVENT" == "push" && "$PLAYWRIGHT_BRANCH" == "main" && "$PLAYWRIGHT_RESULT" == "success" ]]; then
|
|
if grep -qx true "$gallery_dir/publish-stable-baseline"; then
|
|
aws s3 cp \
|
|
"$gallery_dir/manifest.json" \
|
|
"$stable_baseline_uri" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--content-type "application/json" \
|
|
--cache-control "no-store"
|
|
else
|
|
echo "Skipping stable baseline update because a newer successful main run is already published."
|
|
fi
|
|
fi
|
|
aws s3 cp \
|
|
"$history_path" \
|
|
"$bucket_uri/history.json" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--content-type "application/json" \
|
|
--cache-control "no-store"
|
|
aws s3 cp \
|
|
"$dashboard_path" \
|
|
"$bucket_uri/index.html" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--content-type "text/html" \
|
|
--cache-control "no-store"
|
|
|
|
stable_screenshots_url=""
|
|
latest_pr_run="false"
|
|
if [[ -n "${PLAYWRIGHT_PR_NUMBER:-}" ]]; then
|
|
stable_screenshots_url="$public_base_url/prs/$PLAYWRIGHT_PR_NUMBER/index.html"
|
|
latest_pr_run_key="$(jq -r --argjson number "$PLAYWRIGHT_PR_NUMBER" '
|
|
[.[] | select(.pullRequestNumber == $number)][0] |
|
|
if . then .id + "-" + (.attempt | tostring) else empty end
|
|
' "$history_path")"
|
|
if [[ "$latest_pr_run_key" == "$run_key" ]]; then
|
|
aws s3 cp \
|
|
"$gallery_dir/index.html" \
|
|
"$bucket_uri/prs/$PLAYWRIGHT_PR_NUMBER/index.html" \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
--content-type "text/html" \
|
|
--cache-control "no-store"
|
|
latest_pr_run="true"
|
|
else
|
|
echo "Skipping stable PR gallery update because a newer run is already published."
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
|
|
printf 'latest_pr_run=%s\n' "$latest_pr_run" >> "$GITHUB_OUTPUT"
|
|
printf 'stable_screenshots_url=%s\n' "$stable_screenshots_url" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
summary=$(cat <<EOF
|
|
### Playwright visual report
|
|
- [Screenshot gallery]($screenshots_url)
|
|
- [Dashboard]($public_base_url/index.html)
|
|
EOF
|
|
)
|
|
|
|
if [[ -n "$report_url" ]]; then
|
|
summary="$summary
|
|
- [Full report]($report_url)"
|
|
fi
|
|
if [[ -n "$pull_request_url" ]]; then
|
|
summary="$summary
|
|
- [Pull request]($pull_request_url)"
|
|
if [[ -n "$stable_screenshots_url" ]]; then
|
|
summary="$summary
|
|
- [Latest screenshots for this PR]($stable_screenshots_url)"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
|
|
printf '%s\n' "$summary" >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
printf '%s\n' "$summary"
|
|
fi
|