mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-04 11:39:09 +00:00
Compare commits
4 Commits
test/error
...
sno-deploy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c0bdf5af7 | ||
|
|
604d9e70f6 | ||
|
|
01a2ca3bcf | ||
|
|
2a466af154 |
153
.github/workflows/ci-tests-e2e.yaml
vendored
153
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -1,12 +1,12 @@
|
||||
# Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages
|
||||
name: 'CI: Tests E2E'
|
||||
name: "CI: Tests E2E"
|
||||
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, core/*, desktop/*]
|
||||
pull_request:
|
||||
branches-ignore: [wip/*, draft/*, temp/*]
|
||||
workflow_dispatch:
|
||||
branches-ignore:
|
||||
[wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -15,56 +15,65 @@ concurrency:
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Setup Test Environment, build frontend but do not start server yet
|
||||
- name: Setup ComfyUI server
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
include_build_step: true
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright # Setup Playwright and cache browsers
|
||||
|
||||
# Upload only built dist/ (containerized test jobs will pnpm install without cache)
|
||||
- name: Upload built frontend
|
||||
uses: actions/upload-artifact@v6
|
||||
# Save the entire workspace as cache for later test jobs to restore
|
||||
- name: Generate cache key
|
||||
id: cache-key
|
||||
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
|
||||
- name: Save cache
|
||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
retention-days: 1
|
||||
path: .
|
||||
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
|
||||
|
||||
# Sharded chromium tests
|
||||
playwright-tests-chromium-sharded:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
container:
|
||||
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.12
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shardTotal: [8]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Download built frontend
|
||||
uses: actions/download-artifact@v7
|
||||
# download built frontend repo from setup job
|
||||
- name: Wait for cache propagation
|
||||
run: sleep 10
|
||||
- name: Restore cached setup
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
fail-on-cache-miss: true
|
||||
path: .
|
||||
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
||||
|
||||
- name: Start ComfyUI server
|
||||
uses: ./.github/actions/start-comfyui-server
|
||||
# Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job
|
||||
- name: Setup ComfyUI server
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
with:
|
||||
launch_server: true
|
||||
- name: Setup nodejs, pnpm, reuse built frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Install frontend deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# Run sharded tests (browsers pre-installed in container)
|
||||
# Run sharded tests and upload sharded reports
|
||||
- name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
|
||||
id: playwright
|
||||
run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
|
||||
@@ -72,7 +81,7 @@ jobs:
|
||||
PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report
|
||||
|
||||
- name: Upload blob report
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: blob-report-chromium-${{ matrix.shardIndex }}
|
||||
@@ -84,70 +93,69 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.12
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Download built frontend
|
||||
uses: actions/download-artifact@v7
|
||||
# download built frontend repo from setup job
|
||||
- name: Wait for cache propagation
|
||||
run: sleep 10
|
||||
- name: Restore cached setup
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
fail-on-cache-miss: true
|
||||
path: .
|
||||
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
||||
|
||||
- name: Start ComfyUI server
|
||||
uses: ./.github/actions/start-comfyui-server
|
||||
# Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job
|
||||
- name: Setup ComfyUI server
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
with:
|
||||
launch_server: true
|
||||
- name: Setup nodejs, pnpm, reuse built frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Install frontend deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# Run tests (browsers pre-installed in container)
|
||||
# Run tests and upload reports
|
||||
- name: Run Playwright tests (${{ matrix.browser }})
|
||||
id: playwright
|
||||
run: pnpm exec playwright test --project=${{ matrix.browser }} --reporter=blob
|
||||
env:
|
||||
PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report
|
||||
|
||||
- name: Generate HTML and JSON reports
|
||||
if: always()
|
||||
run: |
|
||||
# Generate HTML report from blob
|
||||
pnpm exec playwright merge-reports --reporter=html ./blob-report
|
||||
# Generate JSON report separately with explicit output path
|
||||
# Run tests with both HTML and JSON reporters
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||
pnpm exec playwright merge-reports --reporter=json ./blob-report
|
||||
pnpm exec playwright test --project=${{ matrix.browser }} \
|
||||
--reporter=list \
|
||||
--reporter=html \
|
||||
--reporter=json
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-${{ matrix.browser }}
|
||||
path: ./playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
# Merge sharded test reports (no container needed - only runs CLI)
|
||||
# Merge sharded test reports
|
||||
merge-reports:
|
||||
needs: [playwright-tests-chromium-sharded]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Setup Test Environment, we only need playwright to merge reports
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Download blob reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./all-blob-reports
|
||||
pattern: blob-report-chromium-*
|
||||
@@ -156,13 +164,13 @@ jobs:
|
||||
- name: Merge into HTML Report
|
||||
run: |
|
||||
# Generate HTML report
|
||||
pnpm dlx @playwright/test merge-reports --reporter=html ./all-blob-reports
|
||||
pnpm exec playwright merge-reports --reporter=html ./all-blob-reports
|
||||
# Generate JSON report separately with explicit output path
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||
pnpm dlx @playwright/test merge-reports --reporter=json ./all-blob-reports
|
||||
pnpm exec playwright merge-reports --reporter=json ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-chromium
|
||||
path: ./playwright-report/
|
||||
@@ -180,7 +188,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Get start time
|
||||
id: start-time
|
||||
@@ -207,10 +215,10 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download all playwright reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: playwright-report-*
|
||||
path: reports
|
||||
@@ -220,7 +228,6 @@ jobs:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
GITHUB_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
bash ./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||
"${{ github.event.pull_request.number }}" \
|
||||
|
||||
69
.github/workflows/ci-tests-storybook.yaml
vendored
69
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -1,9 +1,10 @@
|
||||
# Description: Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages
|
||||
name: 'CI: Tests Storybook'
|
||||
name: "CI: Tests Storybook"
|
||||
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
# Post starting comment for non-forked PRs
|
||||
@@ -14,8 +15,8 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Post starting comment
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
@@ -36,10 +37,21 @@ jobs:
|
||||
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Storybook
|
||||
run: pnpm build-storybook
|
||||
@@ -58,7 +70,7 @@ jobs:
|
||||
|
||||
- name: Upload Storybook build
|
||||
if: success() && github.event.pull_request.head.repo.fork == false
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static/
|
||||
@@ -75,16 +87,27 @@ jobs:
|
||||
chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Required for Chromatic baseline
|
||||
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Storybook and run Chromatic
|
||||
id: chromatic
|
||||
uses: chromaui/action@07791f8243f4cb2698bf4d00426baf4b2d1cb7e0 # v13.3.5
|
||||
uses: chromaui/action@latest
|
||||
with:
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
buildScriptName: build-storybook
|
||||
@@ -114,18 +137,18 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download Storybook build
|
||||
if: needs.storybook-build.outputs.conclusion == 'success'
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static
|
||||
|
||||
|
||||
- name: Make deployment script executable
|
||||
run: chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
||||
|
||||
|
||||
- name: Deploy Storybook and comment on PR
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
@@ -148,30 +171,30 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Update comment with Chromatic URLs
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
||||
const storybookUrl = '${{ needs.chromatic-deployment.outputs.chromatic-storybook-url }}';
|
||||
|
||||
|
||||
// Find the existing Storybook comment
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: ${{ github.event.pull_request.number }}
|
||||
});
|
||||
|
||||
const storybookComment = comments.find(comment =>
|
||||
|
||||
const storybookComment = comments.find(comment =>
|
||||
comment.body.includes('<!-- STORYBOOK_BUILD_STATUS -->')
|
||||
);
|
||||
|
||||
|
||||
if (storybookComment && buildUrl && storybookUrl) {
|
||||
// Append Chromatic info to existing comment
|
||||
const updatedBody = storybookComment.body.replace(
|
||||
/---\n(.*)$/s,
|
||||
`---\n### 🎨 Chromatic Visual Tests\n- 📊 [View Chromatic Build](${buildUrl})\n- 📚 [View Chromatic Storybook](${storybookUrl})\n\n$1`
|
||||
);
|
||||
|
||||
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
|
||||
122
.github/workflows/deploy-branch-status.yml
vendored
Normal file
122
.github/workflows/deploy-branch-status.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
name: 'Deploy: Branch Status Pages'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- 'CI: Tests Storybook'
|
||||
- 'CI: Tests E2E'
|
||||
types:
|
||||
- completed
|
||||
branches-ignore:
|
||||
- main
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: deploy-branch-status-${{ github.event.workflow_run.head_branch || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to Cloudflare Pages
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'push' ||
|
||||
(github.event.workflow_run.conclusion == 'success')
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Build status pages
|
||||
run: pnpm pages:build
|
||||
|
||||
- name: Install wrangler
|
||||
run: npm install -g wrangler@^4.0.0
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
id: deploy
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
run: |
|
||||
BRANCH="${{ github.event.workflow_run.head_branch || github.ref_name }}"
|
||||
CF_BRANCH=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
|
||||
|
||||
echo "Deploying branch status pages for: $BRANCH (cf: $CF_BRANCH)"
|
||||
|
||||
OUTPUT=$(wrangler pages deploy .pages-dist \
|
||||
--project-name="comfyui-frontend-reports" \
|
||||
--branch="$CF_BRANCH" 2>&1) || {
|
||||
echo "Deployment failed: $OUTPUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
URL=$(echo "$OUTPUT" | grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev\S*' | head -1)
|
||||
URL="${URL:-https://${CF_BRANCH}.comfyui-frontend-reports.pages.dev}"
|
||||
|
||||
echo "url=$URL" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
|
||||
echo "Deployed to: $URL"
|
||||
|
||||
- name: Find PR number
|
||||
id: pr
|
||||
if: github.event_name == 'workflow_run'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const branch = '${{ steps.deploy.outputs.branch }}';
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
head: `${context.repo.owner}:${branch}`,
|
||||
state: 'open'
|
||||
});
|
||||
return prs.length > 0 ? prs[0].number : null;
|
||||
|
||||
- name: Comment on PR
|
||||
if: steps.pr.outputs.result && steps.pr.outputs.result != 'null'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const marker = '<!-- BRANCH_STATUS_DEPLOY -->';
|
||||
const prNumber = ${{ steps.pr.outputs.result }};
|
||||
const url = '${{ steps.deploy.outputs.url }}';
|
||||
|
||||
const body = `${marker}
|
||||
**Branch Status Page** deployed: ${url}
|
||||
|
||||
Includes: Storybook links, Nx graph, Knip report, test report links`;
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const existing = comments.find(c => c.body.includes(marker));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body
|
||||
});
|
||||
}
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -18,7 +18,6 @@ yarn.lock
|
||||
.stylelintcache
|
||||
|
||||
node_modules
|
||||
.pnpm-store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
@@ -79,7 +78,7 @@ templates_repo/
|
||||
vite.config.mts.timestamp-*.mjs
|
||||
|
||||
# Linux core dumps
|
||||
/core
|
||||
core
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
@@ -94,7 +93,6 @@ storybook-static
|
||||
vite.config.*.timestamp*
|
||||
vitest.config.*.timestamp*
|
||||
|
||||
# Weekly docs check output
|
||||
/output.txt
|
||||
|
||||
.amp
|
||||
# Generated reports in .pages (exclude generated, keep HTML templates)
|
||||
/.pages/*/**/*
|
||||
/.pages-dist/
|
||||
|
||||
54
.pages/README.md
Normal file
54
.pages/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Branch Status Pages
|
||||
|
||||
This directory contains the source for branch status pages deployed to **Cloudflare Pages**.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Push to Branch
|
||||
|
|
||||
+-> GitHub Actions (runs tests, deploys artifacts)
|
||||
| +-> Storybook -> comfy-storybook.pages.dev
|
||||
| +-> E2E Tests -> comfyui-playwright-*.pages.dev
|
||||
| +-> Branch Status -> comfyui-frontend-reports.pages.dev
|
||||
|
|
||||
+-> deploy-branch-status.yml
|
||||
|
|
||||
Runs: pnpm pages:build
|
||||
|
|
||||
Deploys to Cloudflare Pages via wrangler
|
||||
```
|
||||
|
||||
## URLs
|
||||
|
||||
- **Production** (main branch): `https://comfyui-frontend-reports.pages.dev`
|
||||
- **Preview** (PR branches): `https://<branch>.comfyui-frontend-reports.pages.dev`
|
||||
|
||||
## What's Included
|
||||
|
||||
1. **Storybook** - Links to Cloudflare Pages deployment
|
||||
2. **Nx Dependency Graph** - Generated during build
|
||||
3. **Playwright Reports** - Links to Cloudflare Pages deployments
|
||||
4. **Vitest Reports** - Links to CI artifacts
|
||||
5. **Coverage Report** - Links to CI artifacts
|
||||
6. **Knip Report** - Generated fresh during build
|
||||
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
pnpm pages:dev # Dev server
|
||||
pnpm pages:build # Build
|
||||
```
|
||||
|
||||
## Secrets
|
||||
|
||||
Required in GitHub Actions:
|
||||
- `CLOUDFLARE_API_TOKEN`
|
||||
- `CLOUDFLARE_ACCOUNT_ID`
|
||||
|
||||
## Files
|
||||
|
||||
- **index.html** - Landing page with links to all reports
|
||||
- **vite.config.ts** - Vite config for building the static site
|
||||
- **knip.html** - Knip markdown report viewer
|
||||
- **playwright-reports.html** - E2E test report viewer
|
||||
211
.pages/index.html
Normal file
211
.pages/index.html
Normal file
@@ -0,0 +1,211 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ComfyUI Frontend - Development Tools</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #52b2bb 100%);
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.header p {
|
||||
font-size: 1.125rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
p,h1,h2,h3,h4,h5,h6 {
|
||||
transform: matrix(1, 0, -0.25, 1, 0, 0);
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.card-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #1a202c;
|
||||
}
|
||||
.card-description {
|
||||
color: #718096;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.card-status {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status-available {
|
||||
background: #c6f6d5;
|
||||
color: #22543d;
|
||||
}
|
||||
.status-loading {
|
||||
background: #e9d8fd;
|
||||
color: #44337a;
|
||||
}
|
||||
.status-unavailable {
|
||||
background: #fed7d7;
|
||||
color: #742a2a;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-top: 3rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.footer a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🎨 ComfyUI Frontend</h1>
|
||||
<p>Development Tools & Documentation</p>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<a href="./storybook/index.html" class="card" data-status="storybook" data-fetch="./storybook/index.html">
|
||||
<div class="card-icon">📚</div>
|
||||
<h2 class="card-title">Storybook</h2>
|
||||
<p class="card-description">Interactive component library and design system documentation</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./nx-graph/index.html" class="card" data-status="nx-graph" data-fetch="./nx-graph/index.html">
|
||||
<div class="card-icon">🔗</div>
|
||||
<h2 class="card-title">Nx Dependency Graph</h2>
|
||||
<p class="card-description">Visual representation of project dependencies and build structure</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./coverage/index.html" class="card" data-status="coverage" data-fetch="./coverage/index.html">
|
||||
<div class="card-icon">📊</div>
|
||||
<h2 class="card-title">Test Coverage</h2>
|
||||
<p class="card-description">Code coverage reports from Vitest unit tests</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./playwright-reports/index.html" class="card" data-status="playwright" data-fetch="./playwright-reports/index.html">
|
||||
<div class="card-icon">🎭</div>
|
||||
<h2 class="card-title">Playwright E2E</h2>
|
||||
<p class="card-description">Browser end-to-end test reports generated by Playwright</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./vitest-reports/index.html" class="card" data-status="vitest-reports" data-fetch="./vitest-reports/index.html">
|
||||
<div class="card-icon">🧪</div>
|
||||
<h2 class="card-title">Vitest Results</h2>
|
||||
<p class="card-description">Interactive test results and reports</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./knip.html" class="card" data-status="knip" data-fetch="./knip/report.md">
|
||||
<div class="card-icon">🔍</div>
|
||||
<h2 class="card-title">Knip Report</h2>
|
||||
<p class="card-description">Unused code and dependency analysis</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>
|
||||
Built from the <strong>main</strong> branch •
|
||||
<a href="https://github.com/Comfy-Org/ComfyUI_frontend" target="_blank">GitHub Repository</a> •
|
||||
<a href="https://docs.comfy.org" target="_blank">Official Documentation</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const cards = Array.from(document.querySelectorAll('.card[data-status]'))
|
||||
|
||||
const setStatus = (card, variant, text) => {
|
||||
const indicator = card.querySelector('[data-status-indicator]')
|
||||
if (!indicator) return
|
||||
indicator.classList.remove('status-loading', 'status-available', 'status-unavailable')
|
||||
indicator.classList.add(`status-${variant}`)
|
||||
indicator.textContent = text
|
||||
}
|
||||
|
||||
const absoluteUrl = (href) => new URL(href, window.location.href).toString()
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), 10000)
|
||||
|
||||
cards.forEach((card) => {
|
||||
const href = card.dataset.fetch || card.getAttribute('href')
|
||||
if (!href) {
|
||||
setStatus(card, 'unavailable', 'No link configured')
|
||||
return
|
||||
}
|
||||
|
||||
fetch(absoluteUrl(href), { cache: 'no-store', signal: controller.signal })
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
setStatus(card, 'available', 'Available')
|
||||
} else {
|
||||
setStatus(card, 'unavailable', `Unavailable (${response.status})`)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
const reason = error.name === 'AbortError' ? 'Timed out' : 'Unavailable'
|
||||
setStatus(card, 'unavailable', reason)
|
||||
})
|
||||
})
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
clearTimeout(timeout)
|
||||
controller.abort()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
87
.pages/knip.html
Normal file
87
.pages/knip.html
Normal file
@@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Knip Report</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
padding: 2rem;
|
||||
background: #1a1a1a;
|
||||
color: #f0f0f0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
h1 {
|
||||
color: #4a9eff;
|
||||
border-bottom: 3px solid #4a9eff;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 1rem 0;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.error {
|
||||
background: #d32f2f;
|
||||
color: #ffebee;
|
||||
}
|
||||
.loading {
|
||||
background: #f57c00;
|
||||
color: #fff3e0;
|
||||
}
|
||||
#content {
|
||||
background: #2a2a2a;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #404040;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧹 Knip Code Quality Report</h1>
|
||||
|
||||
<div id="status" class="status loading">Loading report...</div>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script>
|
||||
async function loadReport() {
|
||||
const statusEl = document.getElementById('status')
|
||||
const contentEl = document.getElementById('content')
|
||||
|
||||
try {
|
||||
const response = await fetch('./knip/report.md')
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const reportText = await response.text()
|
||||
statusEl.style.display = 'none'
|
||||
|
||||
// Wait for marked to be available
|
||||
if (typeof marked !== 'undefined') {
|
||||
contentEl.innerHTML = marked.parse(reportText)
|
||||
} else {
|
||||
contentEl.innerHTML = `<pre>${reportText}</pre>`
|
||||
}
|
||||
} catch (error) {
|
||||
statusEl.className = 'status error'
|
||||
statusEl.textContent = `Failed to load report: ${error.message}`
|
||||
|
||||
contentEl.innerHTML = '<p>The Knip report could not be loaded. This might happen if:</p><ul><li>The report generation failed during build</li><li>No unused code was detected</li><li>Network connectivity issues</li></ul>'
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for marked library to load before running
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadReport)
|
||||
} else {
|
||||
loadReport()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
300
.pages/playwright-reports.html
Normal file
300
.pages/playwright-reports.html
Normal file
@@ -0,0 +1,300 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Playwright E2E Test Reports</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
font-size: 2.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 1rem auto;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #d32f2f;
|
||||
color: #ffebee;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background: #f57c00;
|
||||
color: #fff3e0;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.pass-rate {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.pass-rate.has-failures {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.stat .label {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat .value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat.passed .value {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.stat.failed .value {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.stat.skipped .value {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.stat.flaky .value {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎭 Playwright E2E Test Reports</h1>
|
||||
|
||||
<div id="status" class="status loading">Loading reports...</div>
|
||||
|
||||
<div id="content" class="cards-grid"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function getTestStats(browserName) {
|
||||
try {
|
||||
const reportJsonPath = `./playwright-reports/${browserName}/report.json`
|
||||
const response = await fetch(reportJsonPath)
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`No report.json found for ${browserName}`)
|
||||
return null
|
||||
}
|
||||
|
||||
const reportData = await response.json()
|
||||
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
let skipped = 0
|
||||
let flaky = 0
|
||||
|
||||
// Parse Playwright JSON report format
|
||||
if (reportData.suites) {
|
||||
const countResults = (suites) => {
|
||||
for (const suite of suites) {
|
||||
if (suite.specs) {
|
||||
for (const spec of suite.specs) {
|
||||
if (!spec.tests || spec.tests.length === 0) continue
|
||||
|
||||
const test = spec.tests[0]
|
||||
const results = test.results || []
|
||||
|
||||
// Check if test is flaky (has both pass and fail results)
|
||||
const hasPass = results.some((r) => r.status === 'passed')
|
||||
const hasFail = results.some((r) => r.status === 'failed')
|
||||
|
||||
if (hasPass && hasFail) {
|
||||
flaky++
|
||||
} else if (results.some((r) => r.status === 'passed')) {
|
||||
passed++
|
||||
} else if (results.some((r) => r.status === 'failed')) {
|
||||
failed++
|
||||
} else if (results.some((r) => r.status === 'skipped')) {
|
||||
skipped++
|
||||
}
|
||||
}
|
||||
}
|
||||
if (suite.suites) {
|
||||
countResults(suite.suites)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
countResults(reportData.suites)
|
||||
}
|
||||
|
||||
return { passed, failed, skipped, flaky }
|
||||
} catch (error) {
|
||||
console.error(`Error reading report for ${browserName}:`, error.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function createCard(browserName, stats) {
|
||||
if (!stats) return ''
|
||||
|
||||
const total = stats.passed + stats.failed + stats.skipped + stats.flaky
|
||||
const passRate = total > 0 ? ((stats.passed / total) * 100).toFixed(1) : 0
|
||||
|
||||
return `
|
||||
<a href="./playwright-reports/${browserName}/index.html" class="card">
|
||||
<div class="card-header">
|
||||
<h2>${browserName}</h2>
|
||||
<span class="pass-rate ${stats.failed > 0 ? 'has-failures' : ''}">${passRate}%</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat passed">
|
||||
<span class="label">Passed</span>
|
||||
<span class="value">${stats.passed}</span>
|
||||
</div>
|
||||
<div class="stat failed">
|
||||
<span class="label">Failed</span>
|
||||
<span class="value">${stats.failed}</span>
|
||||
</div>
|
||||
<div class="stat skipped">
|
||||
<span class="label">Skipped</span>
|
||||
<span class="value">${stats.skipped}</span>
|
||||
</div>
|
||||
<div class="stat flaky">
|
||||
<span class="label">Flaky</span>
|
||||
<span class="value">${stats.flaky}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`
|
||||
}
|
||||
|
||||
async function loadReports() {
|
||||
const statusEl = document.getElementById('status')
|
||||
const contentEl = document.getElementById('content')
|
||||
|
||||
try {
|
||||
// Known browser configurations from the workflow
|
||||
const browsers = ['chromium', 'chromium-2x', 'chromium-0.5x', 'mobile-chrome']
|
||||
|
||||
const cards = []
|
||||
|
||||
for (const browser of browsers) {
|
||||
const stats = await getTestStats(browser)
|
||||
if (stats) {
|
||||
cards.push(createCard(browser, stats))
|
||||
console.log(`✓ Found report for ${browser}:`, stats)
|
||||
}
|
||||
}
|
||||
|
||||
if (cards.length === 0) {
|
||||
throw new Error('No valid browser reports found')
|
||||
}
|
||||
|
||||
statusEl.style.display = 'none'
|
||||
contentEl.innerHTML = cards.join('')
|
||||
} catch (error) {
|
||||
statusEl.className = 'status error'
|
||||
statusEl.textContent = `Failed to load reports: ${error.message}`
|
||||
|
||||
contentEl.innerHTML = '<p style="color: white; text-align: center;">The Playwright reports could not be loaded. This might happen if:</p><ul style="color: white; text-align: center; list-style: none;"><li>The report generation failed during build</li><li>No test reports are available yet</li><li>Network connectivity issues</li></ul>'
|
||||
}
|
||||
}
|
||||
|
||||
// Load reports when page loads
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadReports)
|
||||
} else {
|
||||
loadReports()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
51
.pages/vite.config.ts
Normal file
51
.pages/vite.config.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import fs from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
const rootDir = __dirname
|
||||
const outDir = resolve(rootDir, '../.pages-dist')
|
||||
|
||||
const discoverHtmlEntries = () => {
|
||||
const entries = new Map<string, string>()
|
||||
const topLevel = resolve(rootDir, 'index.html')
|
||||
if (fs.existsSync(topLevel)) entries.set('index', topLevel)
|
||||
|
||||
// Directories with pre-built assets (e.g. nx-graph) are copied directly
|
||||
// in build-pages.sh to avoid CSS minification issues
|
||||
const skipDirs = new Set(['nx-graph'])
|
||||
|
||||
for (const dirent of fs.readdirSync(rootDir, { withFileTypes: true })) {
|
||||
if (!dirent.isDirectory() || dirent.name.startsWith('.')) continue
|
||||
if (skipDirs.has(dirent.name)) continue
|
||||
const candidate = resolve(rootDir, dirent.name, 'index.html')
|
||||
if (fs.existsSync(candidate)) entries.set(dirent.name, candidate)
|
||||
}
|
||||
|
||||
return entries.size > 0 ? Object.fromEntries(entries) : undefined
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
root: rootDir,
|
||||
base: '/',
|
||||
appType: 'mpa',
|
||||
logLevel: 'info',
|
||||
publicDir: false,
|
||||
server: {
|
||||
open: '/index.html',
|
||||
fs: {
|
||||
allow: [rootDir],
|
||||
strict: false
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
open: '/index.html'
|
||||
},
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
outDir,
|
||||
copyPublicDir: false,
|
||||
rollupOptions: {
|
||||
input: discoverHtmlEntries()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -8,15 +8,10 @@ const config: KnipConfig = {
|
||||
'src/assets/css/style.css',
|
||||
'src/main.ts',
|
||||
'src/scripts/ui/menu/index.ts',
|
||||
'src/types/index.ts',
|
||||
'src/storybook/mocks/**/*.ts'
|
||||
'src/types/index.ts'
|
||||
],
|
||||
project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}']
|
||||
},
|
||||
'apps/desktop-ui': {
|
||||
entry: ['src/main.ts', 'src/i18n.ts'],
|
||||
project: ['src/**/*.{js,ts,vue}']
|
||||
},
|
||||
'packages/tailwind-utils': {
|
||||
project: ['src/**/*.{js,ts}']
|
||||
},
|
||||
@@ -27,19 +22,49 @@ const config: KnipConfig = {
|
||||
ignoreBinaries: ['python3', 'gh'],
|
||||
ignoreDependencies: [
|
||||
// Weird importmap things
|
||||
'@iconify-json/lucide',
|
||||
'@iconify/json',
|
||||
'@primeuix/forms',
|
||||
'@primeuix/styled',
|
||||
'@primeuix/utils',
|
||||
'@primevue/icons'
|
||||
'@primevue/icons',
|
||||
// Unused but kept for potential future use
|
||||
'@sparkjsdev/spark',
|
||||
'wwobjloader2',
|
||||
'@iconify-json/lucide',
|
||||
'yjs'
|
||||
],
|
||||
ignore: [
|
||||
// Auto generated manager types
|
||||
'src/workbench/extensions/manager/types/generatedManagerTypes.ts',
|
||||
'packages/registry-types/src/comfyRegistryTypes.ts',
|
||||
// Used by a custom node (that should move off of this)
|
||||
'src/scripts/ui/components/splitButton.ts'
|
||||
'src/scripts/ui/components/splitButton.ts',
|
||||
'.pages/vite.config.ts',
|
||||
// Utility files with exports that may be used by extensions or future features
|
||||
'src/constants/uvMirrors.ts',
|
||||
'src/lib/litegraph/src/measure.ts',
|
||||
'src/lib/litegraph/src/widgets/DisconnectedWidget.ts',
|
||||
'src/renderer/extensions/vueNodes/widgets/utils/audioUtils.ts',
|
||||
'src/utils/electronMirrorCheck.ts',
|
||||
'src/renderer/extensions/vueNodes/composables/slotLinkDragContext.ts',
|
||||
'src/types/spatialIndex.ts',
|
||||
'src/lib/litegraph/src/litegraph.ts',
|
||||
'src/utils/vintageClipboard.ts',
|
||||
'src/platform/auth/session/useSessionCookie.ts',
|
||||
'src/platform/support/config.ts',
|
||||
'src/renderer/utils/nodeTypeGuards.ts',
|
||||
'src/schemas/nodeDefSchema.ts',
|
||||
'src/scripts/defaultGraph.ts',
|
||||
'src/scripts/ui.ts',
|
||||
'src/scripts/widgets.ts',
|
||||
'src/services/litegraphService.ts',
|
||||
'src/storybook/mocks/useJobActions.ts',
|
||||
'src/storybook/mocks/useJobList.ts',
|
||||
'src/utils/executableGroupNodeDto.ts',
|
||||
'src/utils/litegraphUtil.ts',
|
||||
'src/lib/litegraph/src/LGraph.ts',
|
||||
'src/schemas/apiSchema.ts',
|
||||
'src/schemas/nodeDef/nodeDefSchemaV2.ts'
|
||||
],
|
||||
compilers: {
|
||||
// https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
"lint:desktop": "nx run @comfyorg/desktop-ui:lint",
|
||||
"locale": "lobe-i18n locale",
|
||||
"oxlint": "oxlint src --type-aware",
|
||||
"pages:build": "bash scripts/build-pages.sh && vite build --config ./.pages/vite.config.ts && cp -r .pages/nx-graph .pages-dist/nx-graph 2>/dev/null || true",
|
||||
"pages:dev": "vite --config ./.pages/vite.config.ts",
|
||||
"preinstall": "pnpm dlx only-allow pnpm",
|
||||
"prepare": "husky || true && git config blame.ignoreRevsFile .git-blame-ignore-revs || true",
|
||||
"preview": "nx preview",
|
||||
|
||||
152
scripts/build-pages.sh
Executable file
152
scripts/build-pages.sh
Executable file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
echo "======================================================================"
|
||||
echo "Building Branch Status Pages"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
|
||||
# Helper function to create placeholder HTML
|
||||
create_placeholder() {
|
||||
local dir="$1"
|
||||
local title="$2"
|
||||
local message="$3"
|
||||
|
||||
mkdir -p "$dir"
|
||||
cat > "$dir/index.html" <<EOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>$title</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1rem;
|
||||
backdrop-filter: blur(10px);
|
||||
max-width: 600px;
|
||||
}
|
||||
h1 { margin-top: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>$title</h1>
|
||||
<p>$message</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Storybook (deployed separately to Cloudflare Pages)
|
||||
# ============================================================================
|
||||
echo "[build-pages] Setting up Storybook"
|
||||
rm -rf ".pages/storybook"
|
||||
|
||||
if [ -d "./storybook-static" ] && [ "$(find ./storybook-static -name '*.html' 2>/dev/null | wc -l)" -gt 0 ]; then
|
||||
echo " Using local Storybook build"
|
||||
cp -r "./storybook-static" ".pages/storybook"
|
||||
else
|
||||
echo " Creating placeholder (Storybook deployed separately to comfy-storybook.pages.dev)"
|
||||
create_placeholder ".pages/storybook" "Storybook" \
|
||||
"Storybook is deployed separately. Check the PR comments for the Cloudflare Pages link."
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Nx Dependency Graph
|
||||
# ============================================================================
|
||||
echo "[build-pages] Generating Nx dependency graph"
|
||||
rm -rf ".pages/nx-graph" && mkdir -p ".pages/nx-graph"
|
||||
|
||||
if pnpm nx graph --file=".pages/nx-graph/index.html" 2>/dev/null; then
|
||||
echo " Nx graph generated"
|
||||
else
|
||||
echo " Nx graph generation failed, creating placeholder"
|
||||
create_placeholder ".pages/nx-graph" "Nx Dependency Graph" \
|
||||
"Graph generation is not available in this environment."
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Playwright E2E Test Reports (deployed separately to Cloudflare Pages)
|
||||
# ============================================================================
|
||||
echo "[build-pages] Setting up Playwright test reports"
|
||||
rm -rf ".pages/playwright-reports" && mkdir -p ".pages/playwright-reports"
|
||||
|
||||
if [ -d "./playwright-report" ] && [ "$(find ./playwright-report -name '*.html' 2>/dev/null | wc -l)" -gt 0 ]; then
|
||||
echo " Using local Playwright reports"
|
||||
cp -r "./playwright-report/"* ".pages/playwright-reports/" 2>/dev/null || true
|
||||
else
|
||||
echo " Creating placeholder (Playwright reports deployed separately)"
|
||||
create_placeholder ".pages/playwright-reports" "E2E Test Reports" \
|
||||
"Playwright reports are deployed separately. Check the PR comments for Cloudflare Pages links."
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Vitest Test Reports
|
||||
# ============================================================================
|
||||
echo "[build-pages] Setting up Vitest test reports"
|
||||
rm -rf ".pages/vitest-reports" && mkdir -p ".pages/vitest-reports"
|
||||
|
||||
create_placeholder ".pages/vitest-reports" "Vitest Test Reports" \
|
||||
"Unit test results are available in CI. Check the GitHub Actions workflow run."
|
||||
|
||||
# ============================================================================
|
||||
# Coverage Report
|
||||
# ============================================================================
|
||||
echo "[build-pages] Setting up coverage report"
|
||||
mkdir -p ".pages/coverage"
|
||||
|
||||
create_placeholder ".pages/coverage" "Coverage Report" \
|
||||
"Code coverage is generated in CI. Check the GitHub Actions workflow run."
|
||||
|
||||
# ============================================================================
|
||||
# Knip Report (Fast - generate fresh)
|
||||
# ============================================================================
|
||||
echo "[build-pages] Generating Knip report"
|
||||
mkdir -p ".pages/knip"
|
||||
rm -f ".pages/knip/report.md"
|
||||
|
||||
if pnpm knip --reporter markdown --no-progress --no-exit-code > ".pages/knip/report.md" 2>/dev/null && [ -s ".pages/knip/report.md" ]; then
|
||||
echo " Knip report generated"
|
||||
else
|
||||
echo " Knip report failed, creating placeholder"
|
||||
cat > ".pages/knip/report.md" <<'EOF'
|
||||
# Knip Report
|
||||
|
||||
> Knip report generation failed
|
||||
|
||||
Knip analysis could not be completed in this build environment.
|
||||
Please check the CI logs for details.
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Summary
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "Build Complete"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
echo "Generated artifacts in ./.pages:"
|
||||
echo ""
|
||||
ls -lh ".pages" 2>/dev/null | tail -n +2 | awk '{print " " $9}'
|
||||
echo ""
|
||||
290
scripts/create-playwright-index.js
Executable file
290
scripts/create-playwright-index.js
Executable file
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Creates an index page for Playwright test reports with test statistics
|
||||
* Reads JSON reports from each browser and creates a landing page with cards
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const reportsDir = path.join(__dirname, '..', '.pages', 'playwright-reports')
|
||||
|
||||
function getTestStats(reportPath) {
|
||||
try {
|
||||
const reportJsonPath = path.join(reportPath, 'report.json')
|
||||
if (!fs.existsSync(reportJsonPath)) {
|
||||
console.warn(`No report.json found at ${reportJsonPath}`)
|
||||
return null
|
||||
}
|
||||
|
||||
const reportData = JSON.parse(fs.readFileSync(reportJsonPath, 'utf-8'))
|
||||
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
let skipped = 0
|
||||
let flaky = 0
|
||||
|
||||
// Parse Playwright JSON report format
|
||||
if (reportData.suites) {
|
||||
const countResults = (suites) => {
|
||||
for (const suite of suites) {
|
||||
if (suite.specs) {
|
||||
for (const spec of suite.specs) {
|
||||
if (!spec.tests || spec.tests.length === 0) continue
|
||||
|
||||
const test = spec.tests[0]
|
||||
const results = test.results || []
|
||||
|
||||
// Check if test is flaky (has both pass and fail results)
|
||||
const hasPass = results.some((r) => r.status === 'passed')
|
||||
const hasFail = results.some((r) => r.status === 'failed')
|
||||
|
||||
if (hasPass && hasFail) {
|
||||
flaky++
|
||||
} else if (results.some((r) => r.status === 'passed')) {
|
||||
passed++
|
||||
} else if (results.some((r) => r.status === 'failed')) {
|
||||
failed++
|
||||
} else if (results.some((r) => r.status === 'skipped')) {
|
||||
skipped++
|
||||
}
|
||||
}
|
||||
}
|
||||
if (suite.suites) {
|
||||
countResults(suite.suites)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
countResults(reportData.suites)
|
||||
}
|
||||
|
||||
return { passed, failed, skipped, flaky }
|
||||
} catch (error) {
|
||||
console.error(`Error reading report at ${reportPath}:`, error.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function generateIndexHtml(browsers) {
|
||||
const cards = browsers
|
||||
.map((browser) => {
|
||||
const { name, stats } = browser
|
||||
if (!stats) return ''
|
||||
|
||||
const total = stats.passed + stats.failed + stats.skipped + stats.flaky
|
||||
const passRate = total > 0 ? ((stats.passed / total) * 100).toFixed(1) : 0
|
||||
|
||||
return `
|
||||
<a href="./${name}/index.html" class="card">
|
||||
<div class="card-header">
|
||||
<h2>${name}</h2>
|
||||
<span class="pass-rate ${stats.failed > 0 ? 'has-failures' : ''}">${passRate}%</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat passed">
|
||||
<span class="label">Passed</span>
|
||||
<span class="value">${stats.passed}</span>
|
||||
</div>
|
||||
<div class="stat failed">
|
||||
<span class="label">Failed</span>
|
||||
<span class="value">${stats.failed}</span>
|
||||
</div>
|
||||
<div class="stat skipped">
|
||||
<span class="label">Skipped</span>
|
||||
<span class="value">${stats.skipped}</span>
|
||||
</div>
|
||||
<div class="stat flaky">
|
||||
<span class="label">Flaky</span>
|
||||
<span class="value">${stats.flaky}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`
|
||||
})
|
||||
.join('')
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Playwright E2E Test Reports</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
font-size: 2.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.pass-rate {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.pass-rate.has-failures {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.stat .label {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat .value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat.passed .value {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.stat.failed .value {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.stat.skipped .value {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.stat.flaky .value {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎭 Playwright E2E Test Reports</h1>
|
||||
<div class="cards-grid">
|
||||
${cards}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(reportsDir)) {
|
||||
console.log(
|
||||
'No playwright reports directory found, skipping index creation'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const browsers = []
|
||||
const browserDirs = fs.readdirSync(reportsDir, { withFileTypes: true })
|
||||
|
||||
for (const dirent of browserDirs) {
|
||||
if (dirent.isDirectory()) {
|
||||
const browserName = dirent.name
|
||||
const browserPath = path.join(reportsDir, browserName)
|
||||
const stats = getTestStats(browserPath)
|
||||
|
||||
if (stats) {
|
||||
browsers.push({ name: browserName, stats })
|
||||
console.log(`✓ Found report for ${browserName}:`, stats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (browsers.length === 0) {
|
||||
console.warn('No valid browser reports found')
|
||||
return
|
||||
}
|
||||
|
||||
const html = generateIndexHtml(browsers)
|
||||
const indexPath = path.join(reportsDir, 'index.html')
|
||||
|
||||
fs.writeFileSync(indexPath, html, 'utf-8')
|
||||
console.log(`✓ Created index page at ${indexPath}`)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -35,6 +35,7 @@
|
||||
"rootDir": "./"
|
||||
},
|
||||
"include": [
|
||||
".pages/*.ts",
|
||||
".storybook/**/*",
|
||||
"eslint.config.ts",
|
||||
"global.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user