mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
- Move github.event.label.name to env var in Remove QA label step - Move github.head_ref to env var RAW_BRANCH in Deploy videos step - Update gpt-5.4 default model to gpt-4o in qa-video-review.ts Co-authored-by: snomiao <7323030+snomiao@users.noreply.github.com>
565 lines
24 KiB
YAML
565 lines
24 KiB
YAML
# Automated QA of ComfyUI frontend using Claude CLI + Playwright MCP.
|
|
# Two modes:
|
|
# Focused (qa-changes label): Linux-only, tests areas affected by PR changes
|
|
# Full (qa-full label): 3-OS matrix, full test plan
|
|
name: 'PR: QA'
|
|
|
|
on:
|
|
pull_request:
|
|
types: [labeled]
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
inputs:
|
|
mode:
|
|
description: 'QA mode'
|
|
type: choice
|
|
options: [focused, full]
|
|
default: focused
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
resolve-matrix:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
os: ${{ steps.set.outputs.os }}
|
|
mode: ${{ steps.set.outputs.mode }}
|
|
skip: ${{ steps.set.outputs.skip }}
|
|
steps:
|
|
- name: Determine QA mode
|
|
id: set
|
|
env:
|
|
LABEL: ${{ github.event.label.name }}
|
|
EVENT_ACTION: ${{ github.event.action }}
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
INPUT_MODE: ${{ inputs.mode }}
|
|
run: |
|
|
FULL=false
|
|
|
|
# Only run on label events if it's one of our labels
|
|
if [ "$EVENT_ACTION" = "labeled" ] && \
|
|
[ "$LABEL" != "qa-changes" ] && [ "$LABEL" != "qa-full" ]; then
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# Full QA triggers
|
|
if [ "$EVENT_NAME" = "workflow_dispatch" ] && \
|
|
[ "$INPUT_MODE" = "full" ]; then
|
|
FULL=true
|
|
fi
|
|
if [ "$LABEL" = "qa-full" ]; then
|
|
FULL=true
|
|
fi
|
|
|
|
if [ "$FULL" = "true" ]; then
|
|
echo 'os=["ubuntu-latest","macos-latest","windows-latest"]' >> "$GITHUB_OUTPUT"
|
|
echo "mode=full" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo 'os=["ubuntu-latest"]' >> "$GITHUB_OUTPUT"
|
|
echo "mode=focused" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
echo "Mode: $([ "$FULL" = "true" ] && echo full || echo focused)"
|
|
|
|
qa:
|
|
needs: resolve-matrix
|
|
if: needs.resolve-matrix.outputs.skip != 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: ${{ fromJson(needs.resolve-matrix.outputs.os) }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
env:
|
|
QA_MODE: ${{ needs.resolve-matrix.outputs.mode }}
|
|
steps:
|
|
- name: Set QA artifacts path
|
|
shell: bash
|
|
run: echo "QA_ARTIFACTS=$RUNNER_TEMP/qa-artifacts" >> "$GITHUB_ENV"
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
ref: ${{ github.head_ref || github.ref }}
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Setup frontend
|
|
uses: ./.github/actions/setup-frontend
|
|
with:
|
|
include_build_step: true
|
|
|
|
- name: Setup and start ComfyUI server
|
|
uses: ./.github/actions/setup-comfyui-server
|
|
with:
|
|
launch_server: 'true'
|
|
|
|
- name: Wait for ComfyUI server
|
|
shell: bash
|
|
run: |
|
|
echo "Waiting for ComfyUI server..."
|
|
for i in $(seq 1 60); do
|
|
if curl -sf http://127.0.0.1:8188/api/system_stats >/dev/null 2>&1; then
|
|
echo "Server ready"; exit 0
|
|
fi; sleep 2
|
|
done
|
|
echo "::error::Server timeout"; exit 1
|
|
|
|
- name: Install Playwright for MCP
|
|
shell: bash
|
|
run: pnpm exec playwright install chromium --with-deps
|
|
|
|
- name: Install Claude Code
|
|
shell: bash
|
|
run: npm install -g @anthropic-ai/claude-code@2.1.71
|
|
|
|
# --- Virtual display + recording: Linux ---
|
|
- name: Setup Xvfb (Linux)
|
|
if: runner.os == 'Linux'
|
|
run: |
|
|
sudo apt-get update -qq && sudo apt-get install -y -qq xvfb ffmpeg x11-xserver-utils >/dev/null 2>&1
|
|
Xvfb :99 -screen 0 1280x720x24 -ac -nolisten tcp &
|
|
echo $! > "${{ runner.temp }}/xvfb.pid"
|
|
sleep 2
|
|
echo "DISPLAY=:99" >> "$GITHUB_ENV"
|
|
# Set black background and normal cursor (removes the default X cursor)
|
|
DISPLAY=:99 xsetroot -solid black -cursor_name left_ptr 2>/dev/null || true
|
|
|
|
- name: Start recording (Linux)
|
|
if: runner.os == 'Linux'
|
|
continue-on-error: true
|
|
run: |
|
|
mkdir -p "$QA_ARTIFACTS"
|
|
ffmpeg -y -f x11grab -video_size 1280x720 -framerate 10 \
|
|
-i :99.0 -c:v libx264 -preset ultrafast -crf 28 \
|
|
-pix_fmt yuv420p "$QA_ARTIFACTS/qa-session.mp4" &
|
|
echo $! > "${{ runner.temp }}/ffmpeg.pid"
|
|
sleep 1
|
|
|
|
# --- Recording: macOS (avfoundation) ---
|
|
- name: Install ffmpeg (macOS)
|
|
if: runner.os == 'macOS'
|
|
run: brew install ffmpeg
|
|
|
|
- name: Grant screen recording permission (macOS)
|
|
if: runner.os == 'macOS'
|
|
run: |
|
|
# Grant kTCCServiceScreenCapture to ffmpeg and bash so avfoundation
|
|
# recording works without the blocking permission dialog.
|
|
FFMPEG_PATH=$(which ffmpeg)
|
|
echo "Granting screen recording permission to: $FFMPEG_PATH"
|
|
# macOS 14+ (Sonoma/Sequoia) TCC.db schema has 17 columns
|
|
for BIN in "$FFMPEG_PATH" "/bin/bash"; do
|
|
sudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" \
|
|
"INSERT OR REPLACE INTO access VALUES('kTCCServiceScreenCapture','${BIN}',1,2,0,1,NULL,NULL,0,'UNUSED',NULL,0,$(date +%s),NULL,NULL,NULL,NULL);" 2>/dev/null \
|
|
|| sudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" \
|
|
"INSERT OR REPLACE INTO access VALUES('kTCCServiceScreenCapture','${BIN}',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,$(date +%s));" 2>/dev/null \
|
|
|| echo "Warning: Could not grant TCC permission to ${BIN}"
|
|
done
|
|
|
|
- name: Start recording (macOS)
|
|
if: runner.os == 'macOS'
|
|
continue-on-error: true
|
|
run: |
|
|
mkdir -p "$QA_ARTIFACTS"
|
|
ffmpeg -f avfoundation -list_devices true -i "" 2>&1 || true
|
|
ffmpeg -y -f avfoundation -framerate 10 -capture_cursor 1 \
|
|
-i "Capture screen 0:none" -c:v libx264 -preset ultrafast -crf 28 \
|
|
-pix_fmt yuv420p "$QA_ARTIFACTS/qa-session.mp4" &
|
|
echo $! > "${{ runner.temp }}/ffmpeg.pid"
|
|
sleep 2
|
|
|
|
# --- Recording: Windows (gdigrab) ---
|
|
- name: Install ffmpeg (Windows)
|
|
if: runner.os == 'Windows'
|
|
shell: pwsh
|
|
run: choco install ffmpeg -y --no-progress
|
|
|
|
- name: Start recording (Windows)
|
|
if: runner.os == 'Windows'
|
|
continue-on-error: true
|
|
shell: bash
|
|
run: |
|
|
mkdir -p "$QA_ARTIFACTS"
|
|
ffmpeg -y -f gdigrab -framerate 10 -i desktop \
|
|
-c:v libx264 -preset ultrafast -crf 28 \
|
|
-pix_fmt yuv420p "$QA_ARTIFACTS/qa-session.mp4" &
|
|
echo $! > "${{ runner.temp }}/ffmpeg.pid"
|
|
sleep 1
|
|
|
|
- name: Create MCP config
|
|
shell: bash
|
|
run: |
|
|
cat > "${{ runner.temp }}/mcp-config.json" <<EOF
|
|
{"mcpServers":{"playwright":{"command":"npx","args":["@playwright/mcp@0.0.68"]}}}
|
|
EOF
|
|
|
|
- name: Get PR diff for focused QA
|
|
if: needs.resolve-matrix.outputs.mode == 'focused'
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
gh pr diff ${{ github.event.pull_request.number || '' }} \
|
|
--repo ${{ github.repository }} > "${{ runner.temp }}/pr-diff.txt" 2>/dev/null || \
|
|
git diff origin/main...HEAD > "${{ runner.temp }}/pr-diff.txt"
|
|
|
|
# Summarize changed files for the prompt
|
|
echo "Changed files:"
|
|
grep '^diff --git' "${{ runner.temp }}/pr-diff.txt" | \
|
|
sed 's|diff --git a/||;s| b/.*||' | sort -u | tee "${{ runner.temp }}/changed-files.txt"
|
|
|
|
- name: Write QA prompt
|
|
shell: bash
|
|
env:
|
|
BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
PR_NUM: ${{ github.event.pull_request.number || 'N/A' }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
OS_LOWER=$(echo "$RUNNER_OS" | tr '[:upper:]' '[:lower:]')
|
|
|
|
if [ "$QA_MODE" = "full" ]; then
|
|
cat > "${{ runner.temp }}/qa-prompt.txt" <<PROMPT
|
|
You are running a FULL automated QA pass on the ComfyUI frontend.
|
|
Read the file .claude/skills/comfy-qa/SKILL.md and follow the FULL QA test plan.
|
|
|
|
Environment: CI=true, OS=${{ runner.os }}
|
|
Server URL: http://127.0.0.1:8188
|
|
Branch: ${BRANCH}
|
|
PR: #${PR_NUM}
|
|
Commit: ${SHA}
|
|
|
|
1. Use playwright MCP tools to navigate http://127.0.0.1:8188
|
|
2. Run the FULL QA test plan from the skill file
|
|
3. Take screenshots of failures or notable states
|
|
4. Save report to docs/qa/ as YYYY-MM-DD-NNN-${OS_LOWER}-report.md
|
|
5. Commit and push the report to this branch
|
|
|
|
Do NOT create a new PR. Do NOT post PR comments.
|
|
Skip tests not available in CI (file dialogs, GPU execution).
|
|
PROMPT
|
|
else
|
|
cat > "${{ runner.temp }}/qa-prompt.txt" <<PROMPT
|
|
You are running a FOCUSED QA pass on a pull request to the ComfyUI frontend.
|
|
Your goal is to verify that the changes in this PR work correctly and don't break related functionality.
|
|
|
|
Environment: CI=true, OS=${{ runner.os }}
|
|
Server URL: http://127.0.0.1:8188
|
|
Branch: ${BRANCH}
|
|
PR: #${PR_NUM}
|
|
Commit: ${SHA}
|
|
|
|
CHANGED FILES:
|
|
$(cat "${{ runner.temp }}/changed-files.txt" 2>/dev/null || echo "Unknown")
|
|
|
|
DIFF (truncated to 500 lines):
|
|
$(head -500 "${{ runner.temp }}/pr-diff.txt" 2>/dev/null || echo "No diff available")
|
|
|
|
Instructions:
|
|
1. Read the diff above to understand what changed in this PR
|
|
2. Use playwright MCP tools to navigate http://127.0.0.1:8188
|
|
3. Test the specific UI areas affected by these changes
|
|
4. Also do a quick smoke test of core functionality (app loads, canvas renders, sidebar works)
|
|
5. Take screenshots of any failures or the areas you tested
|
|
6. Save a concise report to docs/qa/ as YYYY-MM-DD-NNN-${OS_LOWER}-report.md
|
|
7. Commit and push the report to this branch
|
|
|
|
Focus on:
|
|
- Does the changed functionality work as expected?
|
|
- Are there visual regressions in affected areas?
|
|
- Do related features still work?
|
|
|
|
Do NOT run the full QA test plan. Do NOT create a new PR. Do NOT post PR comments.
|
|
Skip tests not available in CI (file dialogs, GPU execution).
|
|
PROMPT
|
|
fi
|
|
|
|
- name: Run Claude QA
|
|
shell: bash
|
|
env:
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
CI: 'true'
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
MAX_TURNS=128
|
|
if [ "$QA_MODE" = "focused" ]; then MAX_TURNS=30; fi
|
|
|
|
cat "${{ runner.temp }}/qa-prompt.txt" | claude --print --verbose \
|
|
--max-turns "$MAX_TURNS" \
|
|
--mcp-config "${{ runner.temp }}/mcp-config.json" \
|
|
--allowedTools "mcp__playwright__browser_navigate,mcp__playwright__browser_snapshot,mcp__playwright__browser_click,mcp__playwright__browser_type,mcp__playwright__browser_press_key,mcp__playwright__browser_take_screenshot,mcp__playwright__browser_hover,mcp__playwright__browser_drag,mcp__playwright__browser_select_option,mcp__playwright__browser_handle_dialog,mcp__playwright__browser_tab_list,mcp__playwright__browser_tab_new,mcp__playwright__browser_tab_select,mcp__playwright__browser_tab_close,mcp__playwright__browser_console_messages,mcp__playwright__browser_resize,mcp__playwright__browser_wait_for,Bash(git add:*),Bash(git commit:*),Bash(git push:*),Bash(git status:*),Bash(git log:*),Bash(git diff:*),Bash(date:*),Bash(ls:*),Bash(mkdir:*),Read,Write,Edit,Glob,Grep"
|
|
|
|
- name: Stop recording
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
PID_FILE="${{ runner.temp }}/ffmpeg.pid"
|
|
if [ -f "$PID_FILE" ]; then
|
|
if [ "$RUNNER_OS" = "Windows" ]; then
|
|
# Graceful stop: taskkill without /F sends WM_CLOSE so ffmpeg
|
|
# can finalize the mp4 (write moov atom). Force-kill as fallback.
|
|
taskkill //PID $(cat "$PID_FILE") 2>/dev/null || true
|
|
sleep 5
|
|
taskkill //F //PID $(cat "$PID_FILE") 2>/dev/null || true
|
|
else
|
|
kill -INT $(cat "$PID_FILE") 2>/dev/null || true
|
|
sleep 3; kill $(cat "$PID_FILE") 2>/dev/null || true
|
|
fi
|
|
fi
|
|
[ -f "$QA_ARTIFACTS/qa-session.mp4" ] && \
|
|
echo "Video: $(du -h "$QA_ARTIFACTS/qa-session.mp4" | cut -f1)" || \
|
|
echo "No video (non-fatal)"
|
|
|
|
- name: Collect artifacts
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
mkdir -p "$QA_ARTIFACTS"
|
|
cp -r docs/qa/* "$QA_ARTIFACTS/" 2>/dev/null || true
|
|
ls -la "$QA_ARTIFACTS/" || true
|
|
|
|
- name: Upload QA artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v6.2.0
|
|
with:
|
|
name: qa-report-${{ runner.os }}-${{ github.run_id }}
|
|
path: ${{ env.QA_ARTIFACTS }}/
|
|
retention-days: 14
|
|
|
|
- name: Cleanup (Linux)
|
|
if: always() && runner.os == 'Linux'
|
|
run: kill $(cat "${{ runner.temp }}/xvfb.pid") 2>/dev/null || true
|
|
|
|
report:
|
|
needs: [resolve-matrix, qa]
|
|
if: always() && github.event.pull_request.number && github.event.pull_request.head.repo.full_name == github.repository
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
steps:
|
|
- name: Download QA artifacts
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
with:
|
|
path: qa-artifacts
|
|
pattern: qa-report-*
|
|
|
|
- name: Install ffmpeg
|
|
run: sudo apt-get update -qq && sudo apt-get install -y -qq ffmpeg >/dev/null 2>&1
|
|
|
|
- name: Deploy videos to Cloudflare Pages
|
|
id: deploy-videos
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
RAW_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
run: |
|
|
npm install -g wrangler@^4.0.0 >/dev/null 2>&1
|
|
|
|
DEPLOY_DIR=$(mktemp -d)
|
|
mkdir -p "$DEPLOY_DIR"
|
|
|
|
for os in Linux macOS Windows; do
|
|
VID="qa-artifacts/qa-report-${os}-${{ github.run_id }}/qa-session.mp4"
|
|
if [ -f "$VID" ]; then
|
|
cp "$VID" "$DEPLOY_DIR/qa-${os}.mp4"
|
|
echo "Found ${os} video ($(du -h "$VID" | cut -f1))"
|
|
|
|
# Generate GIF thumbnail: 8s starting at 10s, 480px wide, 8fps
|
|
ffmpeg -y -ss 10 -i "$VID" -t 8 \
|
|
-vf "fps=8,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse=dither=bayer" \
|
|
-loop 0 "$DEPLOY_DIR/qa-${os}-thumb.gif" 2>/dev/null \
|
|
|| ffmpeg -y -i "$VID" -t 8 \
|
|
-vf "fps=8,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse=dither=bayer" \
|
|
-loop 0 "$DEPLOY_DIR/qa-${os}-thumb.gif" 2>/dev/null \
|
|
|| echo "GIF generation failed for ${os} (non-fatal)"
|
|
fi
|
|
done
|
|
|
|
# Build video cards HTML
|
|
CARDS=""
|
|
ICONS_Linux="🐧" ICONS_macOS="🍎" ICONS_Windows="🪟"
|
|
for os in Linux macOS Windows; do
|
|
eval "ICON=\$ICONS_${os}"
|
|
if [ -f "$DEPLOY_DIR/qa-${os}.mp4" ]; then
|
|
CARDS="${CARDS}<div class=card><video controls autoplay muted loop preload=metadata><source src=qa-${os}.mp4 type=video/mp4></video><div class=card-body><span class=platform><span class=icon>${ICON}</span> ${os}</span><a class=download href=qa-${os}.mp4 download>Download</a></div></div>"
|
|
else
|
|
CARDS="${CARDS}<div class=card><div class=empty-card>No recording available</div><div class=card-body><span class=platform><span class=icon>${ICON}</span> ${os}</span><span class='badge missing'>Missing</span></div></div>"
|
|
fi
|
|
done
|
|
|
|
cat > "$DEPLOY_DIR/index.html" <<INDEXEOF
|
|
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>QA Session Recordings</title>
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}body{background:#0d1117;color:#e6edf3;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;min-height:100vh;padding:2rem 1rem}.container{max-width:1200px;margin:0 auto}header{display:flex;align-items:center;gap:.75rem;margin-bottom:2rem;padding-bottom:1rem;border-bottom:1px solid #30363d}h1{font-size:1.5rem;font-weight:600}.meta{color:#8b949e;font-size:.875rem;margin-top:.25rem}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:1.25rem}.card{background:#161b22;border:1px solid #30363d;border-radius:.5rem;overflow:hidden;transition:border-color .15s}.card:hover{border-color:#58a6ff}.card video{width:100%;display:block;background:#010409;aspect-ratio:16/9;object-fit:contain}.card-body{padding:.75rem 1rem;display:flex;align-items:center;justify-content:space-between}.platform{display:flex;align-items:center;gap:.5rem;font-weight:500}.icon{font-size:1.25rem}.badge{font-size:.75rem;padding:.125rem .5rem;border-radius:999px;background:#1f6feb33;color:#58a6ff;border:1px solid #1f6feb55}.badge.missing{background:#da363333;color:#f85149;border-color:#da363355}.empty-card{display:flex;align-items:center;justify-content:center;min-height:200px;color:#484f58;font-size:.875rem}a.download{color:#58a6ff;text-decoration:none;font-size:.8125rem}a.download:hover{text-decoration:underline}
|
|
</style></head><body><div class=container>
|
|
<header><svg width=28 height=28 viewBox="0 0 24 24" fill=none stroke=#58a6ff stroke-width=2 stroke-linecap=round stroke-linejoin=round><polygon points="23 7 16 12 23 17 23 7"/><rect x=1 y=5 width=15 height=14 rx=2 ry=2/></svg><div><h1>QA Session Recordings</h1><div class=meta>ComfyUI Frontend · Automated QA</div></div></header>
|
|
<div class=grid>${CARDS}</div>
|
|
</div></body></html>
|
|
INDEXEOF
|
|
|
|
# 404 page so Cloudflare Pages returns proper 404 for missing files
|
|
# (instead of SPA fallback serving index.html)
|
|
cat > "$DEPLOY_DIR/404.html" <<'ERROREOF'
|
|
<!DOCTYPE html><html><head><meta charset=utf-8><title>404</title>
|
|
<style>body{background:#0d1117;color:#8b949e;font-family:sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}div{text-align:center}h1{color:#f85149;font-size:3rem;margin-bottom:.5rem}p{font-size:1rem}</style>
|
|
</head><body><div><h1>404</h1><p>File not found. The QA recording may have failed or been cancelled.</p></div></body></html>
|
|
ERROREOF
|
|
|
|
# Sanitize branch name for Cloudflare Pages URL (same rules CF uses)
|
|
BRANCH=$(echo "$RAW_BRANCH" | sed 's/[^a-zA-Z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-28)
|
|
URL=$(wrangler pages deploy "$DEPLOY_DIR" \
|
|
--project-name="comfyui-qa-videos" \
|
|
--branch="$BRANCH" 2>&1 \
|
|
| grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev\S*' | head -1)
|
|
|
|
echo "url=${URL:-https://${BRANCH}.comfyui-qa-videos.pages.dev}" >> "$GITHUB_OUTPUT"
|
|
echo "Deployed to: ${URL}"
|
|
|
|
- name: Post QA comment on PR
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VIDEO_BASE: ${{ steps.deploy-videos.outputs.url }}
|
|
QA_MODE: ${{ needs.resolve-matrix.outputs.mode }}
|
|
run: |
|
|
RUN="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
COMMENT_MARKER="<!-- QA_REPORT_COMMENT -->"
|
|
|
|
MODE_BADGE="🔍 Focused"
|
|
if [ "$QA_MODE" = "full" ]; then MODE_BADGE="🔬 Full (3-OS)"; fi
|
|
|
|
# Build video section with GIF thumbnails linking to full videos
|
|
VIDEO_SECTION=""
|
|
for os in Linux macOS Windows; do
|
|
GIF_URL="${VIDEO_BASE}/qa-${os}-thumb.gif"
|
|
VID_URL="${VIDEO_BASE}/qa-${os}.mp4"
|
|
if curl -sf --head "$VID_URL" >/dev/null 2>&1; then
|
|
if curl -sf --head "$GIF_URL" >/dev/null 2>&1; then
|
|
VIDEO_SECTION="${VIDEO_SECTION}[](${VID_URL})"$'\n'
|
|
else
|
|
VIDEO_SECTION="${VIDEO_SECTION}[${os} video](${VID_URL})"$'\n'
|
|
fi
|
|
fi
|
|
done
|
|
|
|
BODY=$(cat <<EOF
|
|
${COMMENT_MARKER}
|
|
## QA ${MODE_BADGE}
|
|
|
|
${VIDEO_SECTION}
|
|
**Run**: [${RUN}](${RUN}) · [Download artifacts](${RUN}#artifacts) · [All videos](${VIDEO_BASE})
|
|
EOF
|
|
)
|
|
|
|
EXISTING=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
|
|
--jq ".[] | select(.body | contains(\"${COMMENT_MARKER}\")) | .id" | head -1)
|
|
|
|
if [ -n "$EXISTING" ]; then
|
|
gh api --method PATCH "repos/${{ github.repository }}/issues/comments/${EXISTING}" \
|
|
--field body="$BODY"
|
|
else
|
|
gh pr comment ${{ github.event.pull_request.number }} \
|
|
--repo ${{ github.repository }} --body "$BODY"
|
|
fi
|
|
|
|
- name: Remove QA label
|
|
if: >-
|
|
github.event.label.name == 'qa-changes' ||
|
|
github.event.label.name == 'qa-full'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
LABEL: ${{ github.event.label.name }}
|
|
run: |
|
|
gh pr edit ${{ github.event.pull_request.number }} \
|
|
--repo ${{ github.repository }} --remove-label "$LABEL"
|
|
|
|
video-review:
|
|
needs: [qa, report]
|
|
if: always() && github.event.pull_request.number && github.event.pull_request.head.repo.full_name == github.repository
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
|
|
with:
|
|
version: 10
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Install ffmpeg
|
|
run: sudo apt-get update -qq && sudo apt-get install -y -qq ffmpeg >/dev/null 2>&1
|
|
|
|
- name: Download QA artifacts
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
with:
|
|
path: qa-artifacts
|
|
pattern: qa-report-*
|
|
|
|
- name: Run video review
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
run: |
|
|
mkdir -p video-reviews
|
|
for vid in qa-artifacts/qa-report-*/qa-session.mp4; do
|
|
[ -f "$vid" ] || continue
|
|
echo "::group::Reviewing $vid"
|
|
pnpm exec tsx scripts/qa-video-review.ts \
|
|
--artifacts-dir qa-artifacts \
|
|
--output-dir video-reviews \
|
|
--video-file "$vid" \
|
|
--model gpt-4o || true
|
|
echo "::endgroup::"
|
|
done
|
|
|
|
- name: Post video review comment
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
MARKER="<!-- QA_VIDEO_REVIEW_COMMENT -->"
|
|
RUN="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
|
|
# Combine per-platform reports
|
|
REPORT=""
|
|
for f in video-reviews/*-qa-video-report.md; do
|
|
[ -f "$f" ] || continue
|
|
[ -n "$REPORT" ] && REPORT="${REPORT}
|
|
---
|
|
"
|
|
REPORT="${REPORT}$(cat "$f")"
|
|
done
|
|
|
|
[ -z "$REPORT" ] && REPORT="No video reports were generated."
|
|
|
|
BODY="${MARKER}
|
|
## QA Video Report
|
|
|
|
${REPORT}
|
|
|
|
---
|
|
**Run**: [${RUN}](${RUN})"
|
|
|
|
EXISTING=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
|
|
--jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | head -1)
|
|
|
|
if [ -n "$EXISTING" ]; then
|
|
gh api --method PATCH "repos/${{ github.repository }}/issues/comments/${EXISTING}" \
|
|
--field body="$BODY"
|
|
else
|
|
gh pr comment ${{ github.event.pull_request.number }} \
|
|
--repo ${{ github.repository }} --body "$BODY"
|
|
fi
|