mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-10 17:17:55 +00:00
## Summary
Skip secret-backed CI deploy and dispatch work for fork PRs so missing
repo secrets do not fail otherwise valid checks.
## Changes
- **What**: Guard Website E2E report deploy, Vercel website preview
deploy, cloud build dispatch, cloud cleanup dispatch, and Storybook
Chromatic deploy so PR paths only run for same-repo PRs.
- **Dependencies**: None
## Why
Fork `pull_request` runs do not receive repository secrets. Several CI
jobs already separated normal validation from privileged follow-up work,
but some deploy or dispatch steps could still run on fork PRs and fail
only because their secret-backed integration token was empty.
The existing Website E2E fork guard only protected the PR comment job.
It did not protect the earlier Cloudflare report deploy step inside
`website-e2e`, which uses `CLOUDFLARE_API_TOKEN` and
`CLOUDFLARE_ACCOUNT_ID`.
The same failure mode existed in these CI jobs:
- `ci-vercel-website-preview.yaml`: preview deploy uses Vercel and
website API secrets.
- `cloud-dispatch-build.yaml`: preview dispatch uses
`CLOUD_DISPATCH_TOKEN` to call `Comfy-Org/cloud`.
- `cloud-dispatch-cleanup.yaml`: preview cleanup dispatch uses
`CLOUD_DISPATCH_TOKEN`.
- `ci-tests-storybook.yaml`: Chromatic deploy uses
`CHROMATIC_PROJECT_TOKEN`.
`ci-website-build.yaml` was left unchanged. Its Ashby and Cloud nodes
integrations intentionally fall back to committed snapshots when secrets
are missing for preview/local builds, so it is not the same class of
fork-secret failure.
## Review Focus
Confirm fork PRs still run the unprivileged validation/build paths,
while same-repo PRs and non-PR events keep the existing deploy or
dispatch behavior.
## Validation PRs
Both validation PRs compare against `main`.
- Fork PR from `shihchi`:
[#13309](https://github.com/Comfy-Org/ComfyUI_frontend/pull/13309)
- Same-repo PR from `origin`:
[#13310](https://github.com/Comfy-Org/ComfyUI_frontend/pull/13310)
| Workflow | Guarded job or step | Fork #13309 | Same-repo #13310 |
| --- | --- | --- | --- |
| CI: Website E2E | `Upload test report` | success ✅ | success ✅ |
| CI: Website E2E | `Deploy report to Cloudflare` | skipped ❌ | success
✅ |
| CI: Vercel Website Preview | `deploy-preview` | skipped ❌ | success ✅
|
| Cloud Frontend Build Dispatch | `dispatch` | skipped ❌ | success ✅ |
| CI: Tests Storybook | `chromatic-deployment` | skipped ❌ | success ✅ |
Expected result: fork PRs still keep the useful validation artifact
path, but skip secret-backed deploy and dispatch work. Same-repo PRs
keep the privileged behavior.
## Screenshots (if applicable)
N/A, CI-only.
Created by Codex
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Workflow `if` condition changes only; no application code. Same-repo
PR behavior is unchanged when secrets are available.
>
> **Overview**
> Adds **`github.event.pull_request.head.repo.fork == false`** guards so
fork PRs no longer run steps that need repo secrets or trigger external
deploys.
>
> **Website E2E** — the Cloudflare Playwright report deploy step now
runs only on non-PR events or same-repo PRs, so fork runs can still pass
tests and upload artifacts without failing on missing `CLOUDFLARE_*`
secrets.
>
> **Vercel website preview** — the preview deploy job is skipped
entirely for fork PRs (Vercel tokens).
>
> **Storybook Chromatic** — Chromatic deployment on `version-bump-*` PRs
is limited to non-fork PRs (`CHROMATIC_PROJECT_TOKEN`).
>
> **Cloud dispatch** — build and cleanup dispatches to the cloud repo
for preview labels no longer run for fork PRs, aligning with the
existing fork-guard comment in those workflows.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
027aabc9e3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: huang47 <157390+huang47@users.noreply.github.com>
279 lines
10 KiB
YAML
279 lines
10 KiB
YAML
name: 'CI: Website E2E'
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches-ignore: [wip/*, draft/*, temp/*]
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.repository }}-${{ github.head_ref || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
changes:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
outputs:
|
|
app-website-changes: ${{ steps.changes.outputs.app-website-changes }}
|
|
packages-changes: ${{ steps.changes.outputs.packages-changes }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- id: changes
|
|
uses: ./.github/actions/changes-filter
|
|
|
|
website-e2e:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.app-website-changes == 'true' || needs.changes.outputs.packages-changes == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: mcr.microsoft.com/playwright:v1.58.1-noble
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: read
|
|
outputs:
|
|
test-outcome: ${{ steps.tests.outcome }}
|
|
report-url: ${{ steps.deploy.outputs.url }}
|
|
screenshot-failures: ${{ steps.failures.outputs.screenshot }}
|
|
other-failures: ${{ steps.failures.outputs.other }}
|
|
# Evaluated at job level (not from a step) — static expression.
|
|
is-pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Install pnpm
|
|
run: corepack enable && corepack prepare
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build website
|
|
env:
|
|
WEBSITE_GITHUB_STARS_OVERRIDE: 110000
|
|
run: pnpm --filter @comfyorg/website build
|
|
|
|
- name: Run Playwright tests
|
|
id: tests
|
|
run: pnpm --filter @comfyorg/website test:e2e
|
|
|
|
- name: Upload test report
|
|
uses: actions/upload-artifact@v6
|
|
if: ${{ !cancelled() }}
|
|
with:
|
|
name: website-playwright-report
|
|
path: apps/website/playwright-report/
|
|
retention-days: 30
|
|
|
|
- name: Deploy report to Cloudflare
|
|
id: deploy
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
!cancelled() &&
|
|
(
|
|
github.event_name != 'pull_request' ||
|
|
github.event.pull_request.head.repo.fork == false
|
|
)
|
|
}}
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
HEAD_REF: ${{ github.head_ref || github.ref_name }}
|
|
run: |
|
|
BRANCH=$(echo "$HEAD_REF" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g;s/--*/-/g;s/^-\|-$//g')
|
|
DEPLOY_OK=false
|
|
for i in 1 2 3; do
|
|
echo "Deployment attempt $i of 3..."
|
|
OUTPUT=$(npx wrangler@^4.0.0 pages deploy apps/website/playwright-report \
|
|
--project-name=comfyui-website-e2e \
|
|
--branch="$BRANCH" 2>&1) && { DEPLOY_OK=true; break; } || echo "$OUTPUT"
|
|
[ $i -lt 3 ] && sleep 10
|
|
done
|
|
echo "$OUTPUT"
|
|
if [ "$DEPLOY_OK" != "true" ]; then
|
|
echo "::error::All 3 deployment attempts failed"
|
|
exit 1
|
|
fi
|
|
URL=$(echo "$OUTPUT" | grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev\S*' | head -1)
|
|
echo "url=${URL}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Categorize failures
|
|
id: failures
|
|
if: always() && !cancelled() && steps.tests.outcome != 'success'
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const fs = require('fs')
|
|
const report = JSON.parse(fs.readFileSync('apps/website/results.json', 'utf8'))
|
|
|
|
function isFailed(t) { return t.status === 'unexpected' || t.status === 'flaky' }
|
|
function isVisual(spec) {
|
|
return spec.file?.includes('visual') ||
|
|
spec.tests?.some(t => t.results?.some(r => r.error?.message?.includes('toHaveScreenshot')))
|
|
}
|
|
function specsOf(suite) {
|
|
return [
|
|
...(suite.specs || []),
|
|
...(suite.suites || []).flatMap(specsOf)
|
|
]
|
|
}
|
|
|
|
// True: Visual
|
|
// False: Other
|
|
const failed = specsOf(report)
|
|
.flatMap(spec => (spec.tests || [])
|
|
.filter(isFailed)
|
|
.map(() => isVisual(spec)))
|
|
|
|
const screenshotFailures = failed.filter(Boolean).length
|
|
core.setOutput('screenshot', screenshotFailures)
|
|
core.setOutput('other', failed.length - screenshotFailures)
|
|
|
|
- name: Write job summary
|
|
if: always() && !cancelled()
|
|
uses: actions/github-script@v8
|
|
env:
|
|
TEST_OUTCOME: ${{ steps.tests.outcome }}
|
|
REPORT_URL: ${{ steps.deploy.outputs.url }}
|
|
SCREENSHOT_FAILURES: ${{ steps.failures.outputs.screenshot }}
|
|
OTHER_FAILURES: ${{ steps.failures.outputs.other }}
|
|
with:
|
|
script: |
|
|
const passed = process.env.TEST_OUTCOME === 'success'
|
|
const reportUrl = process.env.REPORT_URL
|
|
const screenshotFailures = parseInt(process.env.SCREENSHOT_FAILURES) || 0
|
|
const otherFailures = parseInt(process.env.OTHER_FAILURES) || 0
|
|
|
|
const lines = ['## 🌐 Website E2E', '']
|
|
|
|
if (passed) {
|
|
lines.push('> [!TIP]', '> All tests passed.')
|
|
} else {
|
|
lines.push('> [!CAUTION]', '> Some tests failed.')
|
|
}
|
|
|
|
const rows = [
|
|
['Status', passed ? '✅ Passed' : '❌ Failed'],
|
|
['Report', reportUrl ? `[View Report](${reportUrl})` : '_unavailable_']
|
|
]
|
|
if (!passed) {
|
|
rows.push(
|
|
['Screenshot diffs', String(screenshotFailures)],
|
|
['Other failures', String(otherFailures)]
|
|
)
|
|
}
|
|
lines.push(
|
|
'',
|
|
'| | |',
|
|
'|---|---|',
|
|
...rows.map(([k, v]) => `| **${k}** | ${v} |`)
|
|
)
|
|
|
|
await core.summary.addRaw(lines.join('\n')).write()
|
|
|
|
post-starting-comment:
|
|
# Safe to comment from pull_request trigger: fork PRs are excluded by the guard below.
|
|
# This avoids a ci-*/pr-* workflow_run split for a comment that must appear immediately.
|
|
needs: changes
|
|
if: |
|
|
github.event_name == 'pull_request'
|
|
&& github.event.pull_request.head.repo.fork == false
|
|
&& (needs.changes.outputs.app-website-changes == 'true' || needs.changes.outputs.packages-changes == 'true')
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
contents: read
|
|
concurrency:
|
|
group: website-pr-comment-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: false
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: ./.github/actions/upsert-comment-section
|
|
with:
|
|
pr-number: ${{ github.event.pull_request.number }}
|
|
section-name: e2e
|
|
comment-marker: '<!-- WEBSITE_CI_REPORT -->'
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
section-content: |-
|
|
## 🌐 Website E2E
|
|
<!-- WEBSITE_E2E_STATUS -->
|
|
|
|
> [!NOTE]
|
|
> Tests are running… [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
|
|
post-result-comment:
|
|
needs: website-e2e
|
|
if: always() && !cancelled() && needs.website-e2e.outputs.is-pr == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
contents: read
|
|
concurrency:
|
|
group: website-pr-comment-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: false
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Build e2e section content
|
|
id: content
|
|
uses: actions/github-script@v8
|
|
env:
|
|
TEST_OUTCOME: ${{ needs.website-e2e.outputs.test-outcome }}
|
|
REPORT_URL: ${{ needs.website-e2e.outputs.report-url }}
|
|
SCREENSHOT_FAILURES: ${{ needs.website-e2e.outputs.screenshot-failures }}
|
|
OTHER_FAILURES: ${{ needs.website-e2e.outputs.other-failures }}
|
|
with:
|
|
script: |
|
|
const passed = process.env.TEST_OUTCOME === 'success'
|
|
const reportUrl = process.env.REPORT_URL
|
|
const screenshotFailures = parseInt(process.env.SCREENSHOT_FAILURES) || 0
|
|
const otherFailures = parseInt(process.env.OTHER_FAILURES) || 0
|
|
|
|
const lines = ['## 🌐 Website E2E', '<!-- WEBSITE_E2E_STATUS -->', '']
|
|
|
|
if (passed) {
|
|
lines.push('> [!TIP]', '> All tests passed.')
|
|
} else {
|
|
lines.push('> [!CAUTION]', '> Some tests failed.')
|
|
}
|
|
|
|
const rows = [
|
|
['Status', passed ? '✅ Passed' : '❌ Failed'],
|
|
['Report', reportUrl ? `[View Report](${reportUrl})` : '_unavailable_']
|
|
]
|
|
if (!passed) {
|
|
rows.push(
|
|
['Screenshot diffs', String(screenshotFailures)],
|
|
['Other failures', String(otherFailures)]
|
|
)
|
|
}
|
|
lines.push(
|
|
'',
|
|
'| | |',
|
|
'|---|---|',
|
|
...rows.map(([k, v]) => `| **${k}** | ${v} |`)
|
|
)
|
|
|
|
if (screenshotFailures > 0) {
|
|
const s = screenshotFailures === 1 ? '' : 's'
|
|
lines.push('', `- [ ] Update website screenshots (${screenshotFailures} screenshot diff${s})`)
|
|
}
|
|
if (otherFailures > 0) {
|
|
lines.push(
|
|
'',
|
|
'> [!WARNING]',
|
|
`> ${otherFailures} non-screenshot failure${otherFailures === 1 ? '' : 's'} — these require manual review.`
|
|
)
|
|
}
|
|
|
|
core.setOutput('section-content', lines.join('\n'))
|
|
|
|
- uses: ./.github/actions/upsert-comment-section
|
|
with:
|
|
pr-number: ${{ github.event.pull_request.number }}
|
|
section-name: e2e
|
|
comment-marker: '<!-- WEBSITE_CI_REPORT -->'
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
section-content: ${{ steps.content.outputs.section-content }}
|