Merge main into austin/widgets-v2

This commit is contained in:
Austin Mroz
2025-09-11 13:04:34 -05:00
745 changed files with 44060 additions and 10711 deletions

View File

@@ -1,30 +1,85 @@
# Create Hotfix Release
This command guides you through creating a patch/hotfix release for ComfyUI Frontend with comprehensive safety checks and human confirmations at each step.
This command creates patch/hotfix releases for ComfyUI Frontend by backporting fixes to stable core branches. It handles both automated backports (preferred) and manual cherry-picking (fallback).
**Process Overview:**
1. **Check automated backports first** (via labels)
2. **Skip to version bump** if backports already merged
3. **Manual cherry-picking** if automation failed
4. **Create patch release** with version bump
5. **Publish GitHub release** (manually uncheck "latest")
6. **Update ComfyUI requirements.txt** via PR
<task>
Create a hotfix release by cherry-picking commits or PR commits from main to a core branch: $ARGUMENTS
Create a hotfix release by backporting commits/PRs from main to a core branch: $ARGUMENTS
Expected format: Comma-separated list of commits or PR numbers
Examples:
- `abc123,def456,ghi789` (commits)
- `#1234,#5678` (PRs)
- `abc123,#1234,def456` (mixed)
- `#1234,#5678` (PRs - preferred)
- `abc123,def456` (commit hashes)
- `#1234,abc123` (mixed)
If no arguments provided, the command will help identify the correct core branch and guide you through selecting commits/PRs.
If no arguments provided, the command will guide you through identifying commits/PRs to backport.
</task>
## Prerequisites
Before starting, ensure:
- You have push access to the repository
- GitHub CLI (`gh`) is authenticated
- You're on a clean working tree
- You understand the commits/PRs you're cherry-picking
- Push access to repository
- GitHub CLI (`gh`) authenticated
- Clean working tree
- Understanding of what fixes need backporting
## Hotfix Release Process
### Step 1: Identify Target Core Branch
### Step 1: Try Automated Backports First
**Check if automated backports were attempted:**
1. **For each PR, check existing backport labels:**
```bash
gh pr view #1234 --json labels | jq -r '.labels[].name'
```
2. **If no backport labels exist, add them now:**
```bash
# Add backport labels (this triggers automated backports)
gh pr edit #1234 --add-label "needs-backport"
gh pr edit #1234 --add-label "1.24" # Replace with target version
```
3. **Check for existing backport PRs:**
```bash
# Check for backport PRs created by automation
PR_NUMBER=${ARGUMENTS%%,*} # Extract first PR number from arguments
PR_NUMBER=${PR_NUMBER#\#} # Remove # prefix
gh pr list --search "backport-${PR_NUMBER}-to" --json number,title,state,baseRefName
```
4. **Handle existing backport scenarios:**
**Scenario A: Automated backports already merged**
```bash
# Check if backport PRs were merged to core branches
gh pr list --search "backport-${PR_NUMBER}-to" --state merged
```
- If backport PRs are merged → Skip to Step 10 (Version Bump)
- **CONFIRMATION**: Automated backports completed, proceeding to version bump?
**Scenario B: Automated backport PRs exist but not merged**
```bash
# Show open backport PRs that need merging
gh pr list --search "backport-${PR_NUMBER}-to" --state open
```
- **ACTION REQUIRED**: Merge the existing backport PRs first
- Use: `gh pr merge [PR_NUMBER] --merge` for each backport PR
- After merging, return to this command and skip to Step 10 (Version Bump)
- **CONFIRMATION**: Have you merged all backport PRs? Ready to proceed to version bump?
**Scenario C: No automated backports or they failed**
- Continue to Step 2 for manual cherry-picking
- **CONFIRMATION**: Proceeding with manual cherry-picking because automation failed?
### Step 2: Identify Target Core Branch
1. Fetch the current ComfyUI requirements.txt from master branch:
```bash
@@ -36,7 +91,7 @@ Before starting, ensure:
5. Verify the core branch exists: `git ls-remote origin refs/heads/core/*`
6. **CONFIRMATION REQUIRED**: Is `core/X.Y` the correct target branch?
### Step 2: Parse and Validate Arguments
### Step 3: Parse and Validate Arguments
1. Parse the comma-separated list of commits/PRs
2. For each item:
@@ -49,7 +104,7 @@ Before starting, ensure:
- **CONFIRMATION REQUIRED**: Use merge commit or cherry-pick individual commits?
4. Validate all commit hashes exist in the repository
### Step 3: Analyze Target Changes
### Step 4: Analyze Target Changes
1. For each commit/PR to cherry-pick:
- Display commit hash, author, date
@@ -60,7 +115,7 @@ Before starting, ensure:
2. Identify potential conflicts by checking changed files
3. **CONFIRMATION REQUIRED**: Proceed with these commits?
### Step 4: Create Hotfix Branch
### Step 5: Create Hotfix Branch
1. Checkout the core branch (e.g., `core/1.23`)
2. Pull latest changes: `git pull origin core/X.Y`
@@ -69,7 +124,7 @@ Before starting, ensure:
- Example: `hotfix/1.23.4-20241120`
5. **CONFIRMATION REQUIRED**: Created branch correctly?
### Step 5: Cherry-pick Changes
### Step 6: Cherry-pick Changes
For each commit:
1. Attempt cherry-pick: `git cherry-pick <commit>`
@@ -83,7 +138,7 @@ For each commit:
- Run validation: `pnpm typecheck && pnpm lint`
4. **CONFIRMATION REQUIRED**: Cherry-pick successful and valid?
### Step 6: Create PR to Core Branch
### Step 7: Create PR to Core Branch
1. Push the hotfix branch: `git push origin hotfix/<version>-<timestamp>`
2. Create PR using gh CLI:
@@ -100,7 +155,7 @@ For each commit:
- Impact assessment
5. **CONFIRMATION REQUIRED**: PR created correctly?
### Step 7: Wait for Tests
### Step 8: Wait for Tests
1. Monitor PR checks: `gh pr checks`
2. Display test results as they complete
@@ -111,7 +166,7 @@ For each commit:
4. Wait for all required checks to pass
5. **CONFIRMATION REQUIRED**: All tests passing?
### Step 8: Merge Hotfix PR
### Step 9: Merge Hotfix PR
1. Verify all checks have passed
2. Check for required approvals
@@ -119,7 +174,7 @@ For each commit:
4. Delete the hotfix branch
5. **CONFIRMATION REQUIRED**: PR merged successfully?
### Step 9: Create Version Bump
### Step 10: Create Version Bump
1. Checkout the core branch: `git checkout core/X.Y`
2. Pull latest changes: `git pull origin core/X.Y`
@@ -131,7 +186,7 @@ For each commit:
7. Commit: `git commit -m "[release] Bump version to 1.23.5"`
8. **CONFIRMATION REQUIRED**: Version bump correct?
### Step 10: Create Release PR
### Step 11: Create Release PR
1. Push release branch: `git push origin release/1.23.5`
2. Create PR with Release label:
@@ -184,7 +239,7 @@ For each commit:
```
5. **CONFIRMATION REQUIRED**: Release PR has "Release" label?
### Step 11: Monitor Release Process
### Step 12: Monitor Release Process
1. Wait for PR checks to pass
2. **FINAL CONFIRMATION**: Ready to trigger release by merging?
@@ -199,7 +254,102 @@ For each commit:
- PyPI upload
- pnpm types publication
### Step 12: Post-Release Verification
### Step 13: Manually Publish Draft Release
**CRITICAL**: The release workflow creates a DRAFT release. You must manually publish it:
1. **Go to GitHub Releases:** https://github.com/Comfy-Org/ComfyUI_frontend/releases
2. **Find the DRAFT release** (e.g., "v1.23.5 Draft")
3. **Click "Edit release"**
4. **UNCHECK "Set as the latest release"** ⚠️ **CRITICAL**
- This prevents the hotfix from showing as "latest"
- Main branch should always be "latest release"
5. **Click "Publish release"**
6. **CONFIRMATION REQUIRED**: Draft release published with "latest" unchecked?
### Step 14: Create ComfyUI Requirements.txt Update PR
**IMPORTANT**: Create PR to update ComfyUI's requirements.txt via fork:
1. **Setup fork (if needed):**
```bash
# Check if fork already exists
if gh repo view ComfyUI --json owner | jq -r '.owner.login' | grep -q "$(gh api user --jq .login)"; then
echo "Fork already exists"
else
# Fork the ComfyUI repository
gh repo fork comfyanonymous/ComfyUI --clone=false
echo "Created fork of ComfyUI"
fi
```
2. **Clone fork and create branch:**
```bash
# Clone your fork (or use existing clone)
GITHUB_USER=$(gh api user --jq .login)
if [ ! -d "ComfyUI-fork" ]; then
gh repo clone ${GITHUB_USER}/ComfyUI ComfyUI-fork
fi
cd ComfyUI-fork
git checkout master
git pull origin master
# Create update branch
BRANCH_NAME="update-frontend-${NEW_VERSION}"
git checkout -b ${BRANCH_NAME}
```
3. **Update requirements.txt:**
```bash
# Update the version in requirements.txt
sed -i "s/comfyui-frontend-package==[0-9].*$/comfyui-frontend-package==${NEW_VERSION}/" requirements.txt
# Verify the change
grep "comfyui-frontend-package" requirements.txt
# Commit the change
git add requirements.txt
git commit -m "Bump frontend to ${NEW_VERSION}"
git push origin ${BRANCH_NAME}
```
4. **Create PR from fork:**
```bash
# Create PR using gh CLI from fork
gh pr create \
--repo comfyanonymous/ComfyUI \
--title "Bump frontend to ${NEW_VERSION}" \
--body "$(cat <<EOF
Bump frontend to ${NEW_VERSION}
\`\`\`
python main.py --front-end-version Comfy-Org/ComfyUI_frontend@${NEW_VERSION}
\`\`\`
- Diff: [Comfy-Org/ComfyUI_frontend: v${OLD_VERSION}...v${NEW_VERSION}](https://github.com/Comfy-Org/ComfyUI_frontend/compare/v${OLD_VERSION}...v${NEW_VERSION})
- PyPI Package: https://pypi.org/project/comfyui-frontend-package/${NEW_VERSION}/
- npm Types: https://www.npmjs.com/package/@comfyorg/comfyui-frontend-types/v/${NEW_VERSION}
## Changes
- Fix: [Brief description of hotfixes included]
EOF
)"
```
5. **Clean up:**
```bash
# Return to original directory
cd ..
# Keep fork directory for future updates
echo "Fork directory 'ComfyUI-fork' kept for future use"
```
6. **CONFIRMATION REQUIRED**: ComfyUI requirements.txt PR created from fork?
### Step 15: Post-Release Verification
1. Verify GitHub release:
```bash
@@ -213,12 +363,14 @@ For each commit:
```bash
pnpm view @comfyorg/comfyui-frontend-types@1.23.5
```
4. Generate release summary with:
4. Monitor ComfyUI requirements.txt PR for approval/merge
5. Generate release summary with:
- Version released
- Commits included
- Issues fixed
- Distribution status
5. **CONFIRMATION REQUIRED**: Release completed successfully?
- ComfyUI integration status
6. **CONFIRMATION REQUIRED**: Hotfix release fully completed?
## Safety Checks
@@ -240,19 +392,28 @@ If something goes wrong:
## Important Notes
- **Always try automated backports first** - This command is for when automation fails
- Core branch version will be behind main - this is expected
- The "Release" label triggers the PyPI/npm publication
- **CRITICAL**: Always uncheck "Set as latest release" for hotfix releases
- **Must create ComfyUI requirements.txt PR** - Hotfix isn't complete without it
- PR numbers must include the `#` prefix
- Mixed commits/PRs are supported but review carefully
- Always wait for full test suite before proceeding
## Expected Timeline
## Modern Workflow Context
- Step 1-3: ~10 minutes (analysis)
- Steps 4-6: ~15-30 minutes (cherry-picking)
- Step 7: ~10-20 minutes (tests)
- Steps 8-10: ~10 minutes (version bump)
- Step 11-12: ~15-20 minutes (release)
- Total: ~60-90 minutes
**Primary Backport Method:** Automated via `needs-backport` + `X.YY` labels
**This Command Usage:**
- Smart path detection - skip to version bump if backports already merged
- Fallback to manual cherry-picking only when automation fails/has conflicts
**Complete Hotfix:** Includes GitHub release publishing + ComfyUI requirements.txt integration
This process ensures a safe, verified hotfix release with multiple confirmation points and clear tracking of what changes are being released.
## Workflow Paths
- **Path A:** Backports already merged → Skip to Step 10 (Version Bump)
- **Path B:** Backport PRs need merging → Merge them → Skip to Step 10 (Version Bump)
- **Path C:** No/failed backports → Manual cherry-picking (Steps 2-9) → Version Bump (Step 10)
This process ensures a complete hotfix release with proper GitHub publishing, ComfyUI integration, and multiple safety checkpoints.

View File

@@ -0,0 +1,158 @@
# Setup Repository
Bootstrap the ComfyUI Frontend monorepo with all necessary dependencies and verification checks.
## Overview
This command will:
1. Install pnpm package manager (if not present)
2. Install all project dependencies
3. Verify the project builds successfully
4. Run unit tests to ensure functionality
5. Start development server to verify frontend boots correctly
## Prerequisites Check
First, let's verify the environment:
```bash
# Check Node.js version (should be >= 24)
node --version
# Check if we're in a git repository
git status
```
## Step 1: Install pnpm
```bash
# Check if pnpm is already installed
pnpm --version 2>/dev/null || {
echo "Installing pnpm..."
npm install -g pnpm
}
# Verify pnpm installation
pnpm --version
```
## Step 2: Install Dependencies
```bash
# Install all dependencies using pnpm
echo "Installing project dependencies..."
pnpm install
# Verify node_modules exists and has packages
ls -la node_modules | head -5
```
## Step 3: Verify Build
```bash
# Run TypeScript type checking
echo "Running TypeScript checks..."
pnpm typecheck
# Build the project
echo "Building project..."
pnpm build
# Verify dist folder was created
ls -la dist/
```
## Step 4: Run Unit Tests
```bash
# Run unit tests
echo "Running unit tests..."
pnpm test:unit
# If tests fail, show the output and stop
if [ $? -ne 0 ]; then
echo "❌ Unit tests failed. Please fix failing tests before continuing."
exit 1
fi
echo "✅ Unit tests passed successfully"
```
## Step 5: Verify Development Server
```bash
# Start development server in background
echo "Starting development server..."
pnpm dev &
SERVER_PID=$!
# Wait for server to start (check for port 5173 or similar)
echo "Waiting for server to start..."
sleep 10
# Check if server is running
if curl -s http://localhost:5173 > /dev/null 2>&1; then
echo "✅ Development server started successfully at http://localhost:5173"
# Kill the background server
kill $SERVER_PID
wait $SERVER_PID 2>/dev/null
else
echo "❌ Development server failed to start or is not accessible"
kill $SERVER_PID 2>/dev/null
wait $SERVER_PID 2>/dev/null
exit 1
fi
```
## Step 6: Final Verification
```bash
# Run linting to ensure code quality
echo "Running linter..."
pnpm lint
# Show project status
echo ""
echo "🎉 Repository setup complete!"
echo ""
echo "Available commands:"
echo " pnpm dev - Start development server"
echo " pnpm build - Build for production"
echo " pnpm test:unit - Run unit tests"
echo " pnpm test:component - Run component tests"
echo " pnpm typecheck - Run TypeScript checks"
echo " pnpm lint - Run ESLint"
echo " pnpm format - Format code with Prettier"
echo ""
echo "Next steps:"
echo "1. Run 'pnpm dev' to start developing"
echo "2. Open http://localhost:5173 in your browser"
echo "3. Check README.md for additional setup instructions"
```
## Troubleshooting
If any step fails:
1. **pnpm installation fails**: Try using `curl -fsSL https://get.pnpm.io/install.sh | sh -`
2. **Dependencies fail to install**: Try clearing cache with `pnpm store prune` and retry
3. **Build fails**: Check for TypeScript errors and fix them first
4. **Tests fail**: Review test output and fix failing tests
5. **Dev server fails**: Check if port 5173 is already in use
## Manual Verification Steps
After running the setup, manually verify:
1. **Dependencies installed**: `ls node_modules | wc -l` should show many packages
2. **Build artifacts**: `ls dist/` should show built files
3. **Server accessible**: Open http://localhost:5173 in browser
4. **Hot reload works**: Edit a file and see changes reflect
## Environment Requirements
- Node.js >= 24
- Git repository
- Internet connection for package downloads
- Available ports (typically 5173 for dev server)

View File

@@ -33,3 +33,4 @@ DISABLE_VUE_PLUGINS=false
# Algolia credentials required for developing with the new custom node manager.
ALGOLIA_APP_ID=4E0RO38HS8
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579

View File

@@ -4,3 +4,6 @@
# npm run format on litegraph merge (10,672 insertions, 7,327 deletions across 129 files)
c53f197de2a3e0fa66b16dedc65c131235c1c4b6
# Reorganize renderer components into domain-driven folder structure
c8a83a9caede7bdb5f8598c5492b07d08c339d49

1
.gitattributes vendored
View File

@@ -9,6 +9,7 @@
*.mts text eol=lf
*.ts text eol=lf
*.vue text eol=lf
*.yaml text eol=lf
# Generated files
src/types/comfyRegistryTypes.ts linguist-generated=true

View File

@@ -2,7 +2,7 @@ name: Auto Backport
on:
pull_request_target:
types: [closed]
types: [closed, labeled]
branches: [main]
jobs:
@@ -25,7 +25,27 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Check if backports already exist
id: check-existing
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
# Check for existing backport PRs for this PR number
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
if [ -z "$EXISTING_BACKPORTS" ]; then
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "Found existing backport PRs:"
echo "$EXISTING_BACKPORTS"
echo "skip=true" >> $GITHUB_OUTPUT
echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates"
- name: Extract version labels
if: steps.check-existing.outputs.skip != 'true'
id: versions
run: |
# Extract version labels (e.g., "1.24", "1.22")
@@ -52,6 +72,7 @@ jobs:
echo "Found version labels: ${VERSIONS}"
- name: Backport commits
if: steps.check-existing.outputs.skip != 'true'
id: backport
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
@@ -109,7 +130,7 @@ jobs:
fi
- name: Create PR for each successful backport
if: steps.backport.outputs.success
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
run: |
@@ -141,7 +162,7 @@ jobs:
done
- name: Comment on failures
if: failure() && steps.backport.outputs.failed
if: steps.check-existing.outputs.skip != 'true' && failure() && steps.backport.outputs.failed
env:
GH_TOKEN: ${{ github.token }}
run: |

View File

@@ -12,9 +12,6 @@ jobs:
runs-on: ubuntu-latest
# Only run for PRs from version-bump-* branches or manual triggers
if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'version-bump-')
permissions:
pull-requests: write
issues: write
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -32,29 +29,6 @@ jobs:
node-version: '20'
cache: 'pnpm'
- name: Get current time
id: current-time
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
- name: Comment PR - Build Started
if: github.event_name == 'pull_request'
continue-on-error: true
uses: edumserrano/find-create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: append
body: |
<!-- STORYBOOK_BUILD_STATUS -->
## 🎨 Storybook Build Status
🔄 **Building Storybook and running visual tests...**
⏳ Build started at: ${{ steps.current-time.outputs.time }} UTC
---
*This comment will be updated when the build completes*
- name: Cache tool outputs
uses: actions/cache@v4
@@ -81,37 +55,3 @@ jobs:
autoAcceptChanges: 'main' # Auto-accept changes on main branch
exitOnceUploaded: true # Don't wait for UI tests to complete
- name: Get completion time
id: completion-time
if: always()
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
- name: Comment PR - Build Complete
if: github.event_name == 'pull_request' && always()
continue-on-error: true
uses: edumserrano/find-create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: replace
body: |
<!-- STORYBOOK_BUILD_STATUS -->
## 🎨 Storybook Build Status
${{ steps.chromatic.outcome == 'success' && '✅' || '❌' }} **${{ steps.chromatic.outcome == 'success' && 'Build completed successfully!' || 'Build failed!' }}**
⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC
### 📊 Build Summary
- **Components**: ${{ steps.chromatic.outputs.componentCount || '0' }}
- **Stories**: ${{ steps.chromatic.outputs.testCount || '0' }}
- **Visual changes**: ${{ steps.chromatic.outputs.changeCount || '0' }}
- **Errors**: ${{ steps.chromatic.outputs.errorCount || '0' }}
### 🔗 Links
${{ steps.chromatic.outputs.buildUrl && format('- [📸 View Chromatic Build]({0})', steps.chromatic.outputs.buildUrl) || '' }}
${{ steps.chromatic.outputs.storybookUrl && format('- [📖 Preview Storybook]({0})', steps.chromatic.outputs.storybookUrl) || '' }}
---
${{ steps.chromatic.outcome == 'success' && '🎉 Your Storybook is ready for review!' || '⚠️ Please check the workflow logs for error details.' }}

View File

@@ -47,6 +47,7 @@ jobs:
needs: wait-for-ci
if: needs.wait-for-ci.outputs.should-proceed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -69,19 +70,17 @@ jobs:
pnpm install -g typescript @vue/compiler-sfc
- name: Run Claude PR Review
uses: anthropics/claude-code-action@main
uses: anthropics/claude-code-action@v1.0.6
with:
label_trigger: "claude-review"
direct_prompt: |
prompt: |
Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly.
CRITICAL: You must post individual inline comments using the gh api commands shown in the file.
DO NOT create a summary comment.
Each issue must be posted as a separate inline comment on the specific line of code.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
max_turns: 256
timeout_minutes: 30
allowed_tools: "Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch"
claude_args: "--max-turns 256 --allowedTools 'Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch'"
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -128,45 +128,6 @@ jobs:
echo "- Critical security patches"
echo "- Documentation updates"
- name: Create branch protection rules
if: steps.check_version.outputs.is_minor_bump == 'true' && env.branch_exists != 'true'
env:
GITHUB_TOKEN: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
# Create branch protection using GitHub API
echo "Setting up branch protection for $BRANCH_NAME..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${{ github.repository }}/branches/$BRANCH_NAME/protection" \
-d '{
"required_status_checks": {
"strict": true,
"contexts": ["lint-and-format", "test", "playwright-tests"]
},
"enforce_admins": false,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true
},
"restrictions": null,
"allow_force_pushes": false,
"allow_deletions": false
}')
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" -eq 200 ]] || [[ "$HTTP_CODE" -eq 201 ]]; then
echo "✅ Branch protection successfully applied"
else
echo "⚠️ Failed to apply branch protection (HTTP $HTTP_CODE)"
echo "Response: $BODY"
# Don't fail the workflow, just warn
fi
- name: Post summary
if: steps.check_version.outputs.is_minor_bump == 'true'

View File

@@ -25,6 +25,13 @@ jobs:
key: i18n-tools-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
restore-keys: |
i18n-tools-cache-${{ runner.os }}-
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
@@ -34,7 +41,7 @@ jobs:
run: pnpm dev:electron &
working-directory: ComfyUI_frontend
- name: Update en.json
run: pnpm collect-i18n -- scripts/collect-i18n-general.ts
run: pnpm collect-i18n
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend

View File

@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.event.pull_request.head.sha }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- name: Install pnpm

View File

@@ -0,0 +1,92 @@
name: PR Playwright Deploy (Forks)
on:
workflow_run:
workflows: ["Tests CI"]
types: [requested, completed]
env:
DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
jobs:
deploy-and-comment-forked-pr:
runs-on: ubuntu-latest
if: |
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.head_repository != null &&
github.event.workflow_run.repository != null &&
github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name
permissions:
pull-requests: write
actions: read
steps:
- name: Log workflow trigger info
run: |
echo "Repository: ${{ github.repository }}"
echo "Event: ${{ github.event.workflow_run.event }}"
echo "Head repo: ${{ github.event.workflow_run.head_repository.full_name || 'null' }}"
echo "Base repo: ${{ github.event.workflow_run.repository.full_name || 'null' }}"
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
- name: Checkout repository
uses: actions/checkout@v4
- name: Get PR Number
id: pr
uses: actions/github-script@v7
with:
script: |
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha);
if (!pr) {
console.log('No PR found for SHA:', context.payload.workflow_run.head_sha);
return null;
}
console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`);
return pr.number;
- name: Handle Test Start
if: steps.pr.outputs.result != 'null' && github.event.action == 'requested'
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
"${{ steps.pr.outputs.result }}" \
"${{ github.event.workflow_run.head_branch }}" \
"starting" \
"$(date -u '${{ env.DATE_FORMAT }}')"
- name: Download and Deploy Reports
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
uses: actions/download-artifact@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
pattern: playwright-report-*
path: reports
- name: Handle Test Completion
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
GITHUB_TOKEN: ${{ github.token }}
run: |
# Rename merged report if exists
[ -d "reports/playwright-report-chromium-merged" ] && \
mv reports/playwright-report-chromium-merged reports/playwright-report-chromium
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
"${{ steps.pr.outputs.result }}" \
"${{ github.event.workflow_run.head_branch }}" \
"completed"

View File

@@ -0,0 +1,126 @@
name: PR Storybook Comment
on:
workflow_run:
workflows: ['Chromatic']
types: [requested, completed]
jobs:
comment-storybook:
runs-on: ubuntu-latest
if: >-
github.repository == 'Comfy-Org/ComfyUI_frontend'
&& github.event.workflow_run.event == 'pull_request'
&& startsWith(github.event.workflow_run.head_branch, 'version-bump-')
permissions:
pull-requests: write
actions: read
steps:
- name: Get PR number
id: pr
uses: actions/github-script@v7
with:
script: |
const { data: pullRequests } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`,
});
if (pullRequests.length === 0) {
console.log('No open PR found for this branch');
return null;
}
return pullRequests[0].number;
- name: Log when no PR found
if: steps.pr.outputs.result == 'null'
run: |
echo "⚠️ No open PR found for branch: ${{ github.event.workflow_run.head_branch }}"
echo "Workflow run ID: ${{ github.event.workflow_run.id }}"
echo "Repository: ${{ github.event.workflow_run.repository.full_name }}"
echo "Event: ${{ github.event.workflow_run.event }}"
- name: Get workflow run details
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
id: workflow-run
uses: actions/github-script@v7
with:
script: |
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
return {
conclusion: run.data.conclusion,
html_url: run.data.html_url
};
- name: Get completion time
id: completion-time
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
- name: Comment PR - Storybook Started
if: steps.pr.outputs.result != 'null' && github.event.action == 'requested'
uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0
with:
issue-number: ${{ steps.pr.outputs.result }}
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: replace
body: |
<!-- STORYBOOK_BUILD_STATUS -->
## 🎨 Storybook Build Status
<img alt='comfy-loading-gif' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px' style='vertical-align: middle; margin-right: 4px;' /> **Build is starting...**
⏰ Started at: ${{ steps.completion-time.outputs.time }} UTC
### 🚀 Building Storybook
- 📦 Installing dependencies...
- 🔧 Building Storybook components...
- 🎨 Running Chromatic visual tests...
---
⏱️ Please wait while the Storybook build is in progress...
- name: Comment PR - Storybook Complete
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0
with:
issue-number: ${{ steps.pr.outputs.result }}
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: replace
body: |
<!-- STORYBOOK_BUILD_STATUS -->
## 🎨 Storybook Build Status
${{
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '✅'
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && '⏭️'
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && '🚫'
|| '❌'
}} **${{
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && 'Build completed successfully!'
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'Build skipped.'
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'Build cancelled.'
|| 'Build failed!'
}}**
⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC
### 🔗 Links
- [📊 View Workflow Run](${{ fromJSON(steps.workflow-run.outputs.result).html_url }})
---
${{
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '🎉 Your Storybook is ready for review!'
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && ' Chromatic was skipped for this PR.'
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && ' The Chromatic run was cancelled.'
|| '⚠️ Please check the workflow logs for error details.'
}}

View File

@@ -11,6 +11,13 @@ jobs:
if: github.event.label.name == 'New Browser Test Expectations'
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend

View File

@@ -7,15 +7,12 @@ on:
branches-ignore:
[wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
env:
DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
jobs:
setup:
runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
sanitized-branch: ${{ steps.branch-info.outputs.sanitized }}
playwright-version: ${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}
steps:
- name: Checkout ComfyUI
uses: actions/checkout@v4
@@ -48,30 +45,6 @@ jobs:
cache: 'pnpm'
cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml'
- name: Get current time
id: current-time
run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
- name: Comment PR - Tests Started
if: github.event_name == 'pull_request'
continue-on-error: true
uses: edumserrano/find-create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: '<!-- PLAYWRIGHT_TEST_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: append
body: |
<!-- PLAYWRIGHT_TEST_STATUS -->
---
<img alt='comfy-loading-gif' src="https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
<bold>[${{ steps.current-time.outputs.time }} UTC] Preparing browser tests across multiple browsers...</bold>
---
*This comment will be updated when tests complete*
- name: Cache tool outputs
uses: actions/cache@v4
with:
@@ -94,13 +67,12 @@ jobs:
id: cache-key
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
- name: Generate sanitized branch name
id: branch-info
run: |
# Get branch name and sanitize it for Cloudflare branch names
BRANCH_NAME="${{ github.head_ref || github.ref_name }}"
SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
echo "sanitized=${SANITIZED_BRANCH}" >> $GITHUB_OUTPUT
- name: Playwright Version
id: playwright-version
run: |
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version')
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
working-directory: ComfyUI_frontend
- name: Save cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
@@ -110,17 +82,17 @@ jobs:
ComfyUI_frontend
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
playwright-tests:
# Sharded chromium tests
playwright-tests-chromium-sharded:
needs: setup
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
contents: read
strategy:
fail-fast: false
matrix:
browser: [chromium, chromium-2x, chromium-0.5x, mobile-chrome]
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
steps:
- name: Wait for cache propagation
run: sleep 10
@@ -144,32 +116,85 @@ jobs:
python-version: '3.10'
cache: 'pip'
- name: Get current time
id: current-time
run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
- name: Set project name
id: project-name
- name: Install requirements
run: |
if [ "${{ matrix.browser }}" = "chromium-0.5x" ]; then
echo "name=comfyui-playwright-chromium-0-5x" >> $GITHUB_OUTPUT
else
echo "name=comfyui-playwright-${{ matrix.browser }}" >> $GITHUB_OUTPUT
fi
echo "branch=${{ needs.setup.outputs.sanitized-branch }}" >> $GITHUB_OUTPUT
python -m pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
pip install wait-for-it
working-directory: ComfyUI
- name: Comment PR - Browser Test Started
if: github.event_name == 'pull_request'
continue-on-error: true
uses: edumserrano/find-create-or-update-comment@v3
- name: Cache Playwright Browsers
uses: actions/cache@v4
id: cache-playwright-browsers
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: '<!-- PLAYWRIGHT_TEST_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: append
body: |
<img alt='comfy-loading-gif' src="https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
<bold>${{ matrix.browser }}</bold>: Running tests...
path: '~/.cache/ms-playwright'
key: '${{ runner.os }}-playwright-browsers-${{ needs.setup.outputs.playwright-version }}'
- name: Install Playwright Browsers
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Install Playwright Browsers (operating system dependencies)
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps
working-directory: ComfyUI_frontend
- name: Start ComfyUI server
run: |
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist &
wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI
- name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
id: playwright
run: npx playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
working-directory: ComfyUI_frontend
env:
PLAYWRIGHT_BLOB_OUTPUT_DIR: ../blob-report
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: blob-report-chromium-${{ matrix.shardIndex }}
path: blob-report/
retention-days: 1
playwright-tests:
# Ideally, each shard runs test in 6 minutes, but allow up to 15 minutes
timeout-minutes: 15
needs: setup
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
steps:
- name: Wait for cache propagation
run: sleep 10
- name: Restore cached setup
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
with:
fail-on-cache-miss: true
path: |
ComfyUI
ComfyUI_frontend
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: 'pip'
- name: Install requirements
run: |
@@ -179,215 +204,145 @@ jobs:
pip install wait-for-it
working-directory: ComfyUI
- name: Cache Playwright Browsers
uses: actions/cache@v4
id: cache-playwright-browsers
with:
path: '~/.cache/ms-playwright'
key: '${{ runner.os }}-playwright-browsers-${{ needs.setup.outputs.playwright-version }}'
- name: Install Playwright Browsers
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Install Playwright Browsers (operating system dependencies)
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps
working-directory: ComfyUI_frontend
- name: Start ComfyUI server
run: |
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist &
wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}-${{ matrix.browser }}
restore-keys: |
playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}-
playwright-browsers-${{ runner.os }}-
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Install Wrangler
run: pnpm install -g wrangler
- name: Run Playwright tests (${{ matrix.browser }})
id: playwright
run: npx playwright test --project=${{ matrix.browser }} --reporter=html
working-directory: ComfyUI_frontend
- uses: actions/upload-artifact@v4
if: always() # note: use always() to allow results to be upload/report even tests failed.
if: always()
with:
name: playwright-report-${{ matrix.browser }}
path: ComfyUI_frontend/playwright-report/
retention-days: 30
- name: Deploy to Cloudflare Pages (${{ matrix.browser }})
id: cloudflare-deploy
if: always()
continue-on-error: true
run: |
# Retry logic for wrangler deploy (3 attempts)
RETRY_COUNT=0
MAX_RETRIES=3
SUCCESS=false
while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ $SUCCESS = false ]; do
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "Deployment attempt $RETRY_COUNT of $MAX_RETRIES..."
if npx wrangler pages deploy ComfyUI_frontend/playwright-report --project-name=${{ steps.project-name.outputs.name }} --branch=${{ steps.project-name.outputs.branch }}; then
SUCCESS=true
echo "Deployment successful on attempt $RETRY_COUNT"
else
echo "Deployment failed on attempt $RETRY_COUNT"
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
echo "Retrying in 10 seconds..."
sleep 10
fi
fi
done
if [ $SUCCESS = false ]; then
echo "All deployment attempts failed"
exit 1
fi
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
# Merge sharded test reports
merge-reports:
needs: [playwright-tests-chromium-sharded]
runs-on: ubuntu-latest
if: ${{ !cancelled() }}
steps:
- name: Checkout ComfyUI_frontend
uses: actions/checkout@v4
with:
repository: 'Comfy-Org/ComfyUI_frontend'
path: 'ComfyUI_frontend'
- name: Save deployment info for summary
if: always()
run: |
mkdir -p deployment-info
# Use step conclusion to determine test result
if [ "${{ steps.playwright.conclusion }}" = "success" ]; then
EXIT_CODE="0"
else
EXIT_CODE="1"
fi
DEPLOYMENT_URL="${{ steps.cloudflare-deploy.outputs.deployment-url || steps.cloudflare-deploy.outputs.url || format('https://{0}.{1}.pages.dev', steps.project-name.outputs.branch, steps.project-name.outputs.name) }}"
echo "${{ matrix.browser }}|${EXIT_CODE}|${DEPLOYMENT_URL}" > deployment-info/${{ matrix.browser }}.txt
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Upload deployment info
if: always()
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'pnpm'
cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml'
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
working-directory: ComfyUI_frontend
- name: Download blob reports
uses: actions/download-artifact@v4
with:
path: ComfyUI_frontend/all-blob-reports
pattern: blob-report-chromium-*
merge-multiple: true
- name: Merge into HTML Report
run: npx playwright merge-reports --reporter html ./all-blob-reports
working-directory: ComfyUI_frontend
- name: Upload HTML report
uses: actions/upload-artifact@v4
with:
name: deployment-info-${{ matrix.browser }}
path: deployment-info/
retention-days: 1
name: playwright-report-chromium
path: ComfyUI_frontend/playwright-report/
retention-days: 30
- name: Get completion time
id: completion-time
if: always()
run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
#### BEGIN Deployment and commenting (non-forked PRs only)
# when using pull_request event, we have permission to comment directly
# if its a forked repo, we need to use workflow_run event in a separate workflow (pr-playwright-deploy.yaml)
- name: Comment PR - Browser Test Complete
if: always() && github.event_name == 'pull_request'
continue-on-error: true
uses: edumserrano/find-create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: '<!-- PLAYWRIGHT_TEST_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: append
body: |
${{ steps.playwright.conclusion == 'success' && '✅' || '❌' }} **${{ matrix.browser }}**: ${{ steps.playwright.conclusion == 'success' && 'Tests passed!' || 'Tests failed!' }} [View Report](${{ steps.cloudflare-deploy.outputs.deployment-url || format('https://{0}.{1}.pages.dev', steps.project-name.outputs.branch, steps.project-name.outputs.name) }})
comment-summary:
needs: playwright-tests
# Post starting comment for non-forked PRs
comment-on-pr-start:
runs-on: ubuntu-latest
if: always() && github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
permissions:
pull-requests: write
steps:
- name: Download all deployment info
- name: Checkout repository
uses: actions/checkout@v4
- name: Get start time
id: start-time
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
- name: Post starting comment
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
"${{ github.event.pull_request.number }}" \
"${{ github.head_ref }}" \
"starting" \
"${{ steps.start-time.outputs.time }}"
# Deploy and comment for non-forked PRs only
deploy-and-comment:
needs: [playwright-tests, merge-reports]
runs-on: ubuntu-latest
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all playwright reports
uses: actions/download-artifact@v4
with:
pattern: deployment-info-*
merge-multiple: true
path: deployment-info
- name: Get completion time
id: completion-time
run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
- name: Generate comment body
id: comment-body
pattern: playwright-report-*
path: reports
- name: Make deployment script executable
run: chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
- name: Deploy reports and comment on PR
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
GITHUB_TOKEN: ${{ github.token }}
run: |
echo "<!-- PLAYWRIGHT_TEST_STATUS -->" > comment.md
echo "## 🎭 Playwright Test Results" >> comment.md
echo "" >> comment.md
# Check if all tests passed
ALL_PASSED=true
for file in deployment-info/*.txt; do
if [ -f "$file" ]; then
browser=$(basename "$file" .txt)
info=$(cat "$file")
exit_code=$(echo "$info" | cut -d'|' -f2)
if [ "$exit_code" != "0" ]; then
ALL_PASSED=false
break
fi
fi
done
if [ "$ALL_PASSED" = "true" ]; then
echo "✅ **All tests passed across all browsers!**" >> comment.md
else
echo "❌ **Some tests failed!**" >> comment.md
fi
echo "" >> comment.md
echo "⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC" >> comment.md
echo "" >> comment.md
echo "### 📊 Test Reports by Browser" >> comment.md
for file in deployment-info/*.txt; do
if [ -f "$file" ]; then
browser=$(basename "$file" .txt)
info=$(cat "$file")
exit_code=$(echo "$info" | cut -d'|' -f2)
url=$(echo "$info" | cut -d'|' -f3)
if [ "$exit_code" = "0" ]; then
status="✅"
else
status="❌"
fi
echo "- $status **$browser**: [View Report]($url)" >> comment.md
fi
done
echo "" >> comment.md
echo "---" >> comment.md
if [ "$ALL_PASSED" = "true" ]; then
echo "🎉 Your tests are passing across all browsers!" >> comment.md
else
echo "⚠️ Please check the test reports for details on failures." >> comment.md
fi
- name: Comment PR - Tests Complete
continue-on-error: true
uses: edumserrano/find-create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: '<!-- PLAYWRIGHT_TEST_STATUS -->'
comment-author: 'github-actions[bot]'
edit-mode: replace
body-path: comment.md
- name: Check test results and fail if needed
run: |
# Check if all tests passed and fail the job if not
ALL_PASSED=true
for file in deployment-info/*.txt; do
if [ -f "$file" ]; then
info=$(cat "$file")
exit_code=$(echo "$info" | cut -d'|' -f2)
if [ "$exit_code" != "0" ]; then
ALL_PASSED=false
break
fi
fi
done
if [ "$ALL_PASSED" = "false" ]; then
echo "❌ Tests failed in one or more browsers. Failing the CI job."
exit 1
else
echo "✅ All tests passed across all browsers!"
fi
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
"${{ github.event.pull_request.number }}" \
"${{ github.head_ref }}" \
"completed"
#### END Deployment and commenting (non-forked PRs only)

View File

@@ -35,12 +35,12 @@ jobs:
electron-types-tools-cache-${{ runner.os }}-
- name: Update electron types
run: pnpm install @comfyorg/comfyui-electron-types@latest
run: pnpm install --workspace-root @comfyorg/comfyui-electron-types@latest
- name: Get new version
id: get-version
run: |
NEW_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('./pnpm-lock.yaml')).packages['node_modules/@comfyorg/comfyui-electron-types'].version)")
NEW_VERSION=$(pnpm list @comfyorg/comfyui-electron-types --json --depth=0 | jq -r '.[0].version')
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Create Pull Request

View File

@@ -121,4 +121,4 @@ jobs:
labels: Manager
delete-branch: true
add-paths: |
src/types/generatedManagerTypes.ts
src/types/generatedManagerTypes.ts

11
.gitignore vendored
View File

@@ -22,6 +22,7 @@ dist-ssr
*.local
# Claude configuration
.claude/*.local.json
CLAUDE.local.md
# Editor directories and files
.vscode/*
@@ -50,6 +51,7 @@ tests-ui/workflows/examples
/blob-report/
/playwright/.cache/
browser_tests/**/*-win32.png
browser-tests/local/
.env
@@ -76,3 +78,12 @@ vite.config.mts.timestamp-*.mjs
*storybook.log
storybook-static
.nx/cache
.nx/workspace-data
.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md
vite.config.*.timestamp*
vitest.config.*.timestamp*

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
npx lint-staged
npx tsx scripts/check-unused-i18n-keys.ts
pnpm exec lint-staged
pnpm exec tsx scripts/check-unused-i18n-keys.ts

5
.husky/pre-push Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Run Knip with cache via package script
pnpm knip

View File

@@ -1,12 +0,0 @@
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@executeautomation/playwright-mcp-server"]
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
24

View File

@@ -4,17 +4,26 @@
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Light theme default */
body {
background-color: #ffffff;
color: #1a1a1a;
/* Light theme default - with explicit color to override media queries */
body:not(.dark-theme) {
background-color: #fff !important;
color: #000 !important;
}
/* Override browser dark mode preference for light theme */
@media (prefers-color-scheme: dark) {
body:not(.dark-theme) {
color: #000 !important;
--fg-color: #000 !important;
--bg-color: #fff !important;
}
}
/* Dark theme styles */
body.dark-theme,
.dark-theme body {
background-color: #0a0a0a;
color: #e5e5e5;
background-color: #202020;
color: #fff;
}
/* Ensure Storybook canvas follows theme */
@@ -24,11 +33,32 @@
.dark-theme .sb-show-main,
.dark-theme .docs-story {
background-color: #0a0a0a !important;
background-color: #202020 !important;
}
/* Fix for Storybook controls panel in dark mode */
.dark-theme .docblock-argstable-body {
color: #e5e5e5;
/* CSS Variables for theme consistency */
body:not(.dark-theme) {
--fg-color: #000;
--bg-color: #fff;
--content-bg: #e0e0e0;
--content-fg: #000;
--content-hover-bg: #adadad;
--content-hover-fg: #000;
}
body.dark-theme {
--fg-color: #fff;
--bg-color: #202020;
--content-bg: #4e4e4e;
--content-fg: #fff;
--content-hover-bg: #222;
--content-hover-fg: #fff;
}
/* Override Storybook's problematic & selector styles */
/* Reset only the specific properties that Storybook injects */
li+li {
margin: 0;
padding: revert-layer;
}
</style>

44
.vscode/tailwind.json vendored
View File

@@ -2,12 +2,32 @@
"version": 1.1,
"atDirectives": [
{
"name": "@tailwind",
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
"name": "@import",
"description": "Use the `@import` directive to inline CSS files, including Tailwind itself, into your stylesheet.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
"url": "https://tailwindcss.com/docs/functions-and-directives#import"
}
]
},
{
"name": "@theme",
"description": "Use the `@theme` directive to define custom design tokens like fonts, colors, and breakpoints.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#theme"
}
]
},
{
"name": "@layer",
"description": "Use the `@layer` directive inside `@theme` to organize custom styles into different layers like `base`, `components`, and `utilities`.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#layer"
}
]
},
@@ -22,32 +42,32 @@
]
},
{
"name": "@responsive",
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
"name": "@config",
"description": "Use the `@config` directive to load a legacy JavaScript-based Tailwind configuration file.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
"url": "https://tailwindcss.com/docs/functions-and-directives#config"
}
]
},
{
"name": "@screen",
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
"name": "@reference",
"description": "Use the `@reference` directive to import theme variables, custom utilities, and custom variants from other files without duplicating CSS.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
"url": "https://tailwindcss.com/docs/functions-and-directives#reference"
}
]
},
{
"name": "@variants",
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
"name": "@plugin",
"description": "Use the `@plugin` directive to load a legacy JavaScript-based Tailwind plugin.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
"url": "https://tailwindcss.com/docs/functions-and-directives#plugin"
}
]
}

View File

@@ -1,22 +1,52 @@
# ComfyUI Frontend Project Guidelines
## Repository Setup
For first-time setup, use the Claude command:
```
/setup_repo
```
This bootstraps the monorepo with dependencies, builds, tests, and dev server verification.
**Prerequisites:** Node.js >= 24, Git repository, available ports (5173, 6006)
## Quick Commands
- `pnpm`: See all available commands
- `pnpm dev`: Start development server (port 5173, via nx)
- `pnpm typecheck`: Type checking
- `pnpm lint`: Linting
- `pnpm build`: Build for production (via nx)
- `pnpm lint`: Linting (via nx)
- `pnpm format`: Prettier formatting
- `pnpm test:component`: Run component tests with browser environment
- `pnpm test:unit`: Run all unit tests
- `pnpm test:browser`: Run E2E tests via Playwright
- `pnpm test:unit -- tests-ui/tests/example.test.ts`: Run single test file
- `pnpm storybook`: Start Storybook development server (port 6006)
- `pnpm knip`: Detect unused code and dependencies
## Monorepo Architecture
The project now uses **Nx** for build orchestration and task management:
- **Task Orchestration**: Commands like `dev`, `build`, `lint`, and `test:browser` run via Nx
- **Caching**: Nx provides intelligent caching for faster rebuilds
- **Configuration**: Managed through `nx.json` with plugins for ESLint, Storybook, Vite, and Playwright
- **Dependencies**: Nx handles dependency graph analysis and parallel execution
Key Nx features:
- Build target caching and incremental builds
- Parallel task execution across the monorepo
- Plugin-based architecture for different tools
## Development Workflow
1. Make code changes
2. Run tests (see subdirectory CLAUDE.md files)
3. Run typecheck, lint, format
4. Check README updates
5. Consider docs.comfy.org updates
1. **First-time setup**: Run `/setup_repo` Claude command
2. Make code changes
3. Run tests (see subdirectory CLAUDE.md files)
4. Run typecheck, lint, format
5. Check README updates
6. Consider docs.comfy.org updates
## Git Conventions
@@ -52,6 +82,44 @@ When referencing Comfy-Org repos:
2. Use GitHub API for branches/PRs/metadata
3. Curl GitHub website if needed
## Settings and Feature Flags Quick Reference
### Settings Usage
```typescript
const settingStore = useSettingStore()
const value = settingStore.get('Comfy.SomeSetting') // Get setting
await settingStore.set('Comfy.SomeSetting', newValue) // Update setting
```
### Dynamic Defaults
```typescript
{
id: 'Comfy.Example.Setting',
defaultValue: () => window.innerWidth < 1024 ? 'small' : 'large' // Runtime context
}
```
### Version-Based Defaults
```typescript
{
id: 'Comfy.Example.Feature',
defaultValue: 'legacy',
defaultsByInstallVersion: { '1.25.0': 'enhanced' } // Gradual rollout
}
```
### Feature Flags
```typescript
if (api.serverSupportsFeature('feature_name')) { // Check capability
// Use enhanced feature
}
const value = api.getServerFeature('config_name', defaultValue) // Get config
```
**Documentation:**
- Settings system: `docs/SETTINGS.md`
- Feature flags system: `docs/FEATURE_FLAGS.md`
## Common Pitfalls
- NEVER use `any` type - use proper TypeScript types
@@ -59,3 +127,6 @@ When referencing Comfy-Org repos:
- NEVER use `--no-verify` flag when committing
- NEVER delete or disable tests to make them pass
- NEVER circumvent quality checks
- NEVER use `dark:` prefix - always use `dark-theme:` for dark mode styles, for example: `dark-theme:text-white dark-theme:bg-black`
- NEVER use `:class="[]"` to merge class names - always use `import { cn } from '@/utils/tailwindUtil'`, for example: `<div :class="cn('bg-red-500', { 'bg-blue-500': condition })" />`

View File

@@ -14,4 +14,4 @@
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
# Mask Editor extension
/src/extensions/core/maskeditor.ts @trsommer @Comfy-Org/comfy_frontend_devs
/src/extensions/core/maskeditor.ts @brucew4yn3rp @trsommer @Comfy-Org/comfy_frontend_devs

View File

@@ -17,7 +17,7 @@ Have another idea? Drop into Discord or open an issue, and let's chat!
### Prerequisites & Technology Stack
- **Required Software**:
- Node.js (v16 or later; v24 strongly recommended) and pnpm
- Node.js (v18 or later to build; v24 for vite dev server) and pnpm
- Git for version control
- A running ComfyUI backend instance

View File

@@ -10,7 +10,7 @@ import type { Position } from './types'
* - {@link Mouse.move}
* - {@link Mouse.up}
*/
export interface DragOptions {
interface DragOptions {
button?: 'left' | 'right' | 'middle'
clickCount?: number
steps?: number

View File

@@ -453,6 +453,32 @@ export class ComfyPage {
await workflowsTab.close()
}
/**
* Attach a screenshot to the test report.
* By default, screenshots are only taken in non-CI environments.
* @param name - Name for the screenshot attachment
* @param options - Optional configuration
* @param options.runInCI - Whether to take screenshot in CI (default: false)
* @param options.fullPage - Whether to capture full page (default: false)
*/
async attachScreenshot(
name: string,
options: { runInCI?: boolean; fullPage?: boolean } = {}
) {
const { runInCI = false, fullPage = false } = options
// Skip in CI unless explicitly requested
if (process.env.CI && !runInCI) {
return
}
const testInfo = comfyPageFixture.info()
await testInfo.attach(name, {
body: await this.page.screenshot({ fullPage }),
contentType: 'image/png'
})
}
async resetView() {
if (await this.resetViewButton.isVisible()) {
await this.resetViewButton.click()

View File

@@ -1,5 +1,4 @@
import { test as base } from '@playwright/test'
import { Page } from 'playwright'
import { Page, test as base } from '@playwright/test'
export class UserSelectPage {
constructor(

View File

@@ -1,7 +1,13 @@
import { Locator, Page } from '@playwright/test'
import { Locator, Page, expect } from '@playwright/test'
export class Topbar {
constructor(public readonly page: Page) {}
private readonly menuLocator: Locator
private readonly menuTrigger: Locator
constructor(public readonly page: Page) {
this.menuLocator = page.locator('.comfy-command-menu')
this.menuTrigger = page.locator('.comfyui-logo-wrapper')
}
async getTabNames(): Promise<string[]> {
return await this.page
@@ -15,10 +21,33 @@ export class Topbar {
.innerText()
}
getMenuItem(itemLabel: string): Locator {
/**
* Get a menu item by its label, optionally within a specific parent container
*/
getMenuItem(itemLabel: string, parent?: Locator): Locator {
if (parent) {
return parent.locator(`.p-tieredmenu-item:has-text("${itemLabel}")`)
}
return this.page.locator(`.p-menubar-item-label:text-is("${itemLabel}")`)
}
/**
* Get the visible submenu (last visible submenu in case of nested menus)
*/
getVisibleSubmenu(): Locator {
return this.page.locator('.p-tieredmenu-submenu:visible').last()
}
/**
* Check if a menu item has an active checkmark
*/
async isMenuItemActive(menuItem: Locator): Promise<boolean> {
const checkmark = menuItem.locator('.pi-check')
const classes = await checkmark.getAttribute('class')
return classes ? !classes.includes('invisible') : false
}
getWorkflowTab(tabName: string): Locator {
return this.page
.locator(`.workflow-tabs .workflow-label:has-text("${tabName}")`)
@@ -66,10 +95,50 @@ export class Topbar {
async openTopbarMenu() {
await this.page.waitForTimeout(1000)
await this.page.locator('.comfyui-logo-wrapper').click()
const menu = this.page.locator('.comfy-command-menu')
await menu.waitFor({ state: 'visible' })
return menu
await this.menuTrigger.click()
await this.menuLocator.waitFor({ state: 'visible' })
return this.menuLocator
}
/**
* Close the topbar menu by clicking outside
*/
async closeTopbarMenu() {
await this.page.locator('body').click({ position: { x: 10, y: 10 } })
await expect(this.menuLocator).not.toBeVisible()
}
/**
* Navigate to a submenu by hovering over a menu item
*/
async openSubmenu(menuItemLabel: string): Promise<Locator> {
const menuItem = this.getMenuItem(menuItemLabel)
await menuItem.hover()
const submenu = this.getVisibleSubmenu()
await submenu.waitFor({ state: 'visible' })
return submenu
}
/**
* Get theme menu items and interact with theme switching
*/
async getThemeMenuItems() {
const themeSubmenu = await this.openSubmenu('Theme')
return {
submenu: themeSubmenu,
darkTheme: this.getMenuItem('Dark (Default)', themeSubmenu),
lightTheme: this.getMenuItem('Light', themeSubmenu)
}
}
/**
* Switch to a specific theme
*/
async switchTheme(theme: 'dark' | 'light') {
const { darkTheme, lightTheme } = await this.getThemeMenuItems()
const themeItem = theme === 'dark' ? darkTheme : lightTheme
const themeLabel = themeItem.locator('.p-menubar-item-label')
await themeLabel.click()
}
async triggerTopbarCommand(path: string[]) {
@@ -79,9 +148,7 @@ export class Topbar {
const menu = await this.openTopbarMenu()
const tabName = path[0]
const topLevelMenuItem = this.page.locator(
`.p-menubar-item-label:text-is("${tabName}")`
)
const topLevelMenuItem = this.getMenuItem(tabName)
const topLevelMenu = menu
.locator('.p-tieredmenu-item')
.filter({ has: topLevelMenuItem })

View File

@@ -134,7 +134,7 @@ export class SubgraphSlotReference {
}
}
export class NodeSlotReference {
class NodeSlotReference {
constructor(
readonly type: 'input' | 'output',
readonly index: number,
@@ -201,7 +201,7 @@ export class NodeSlotReference {
}
}
export class NodeWidgetReference {
class NodeWidgetReference {
constructor(
readonly index: number,
readonly node: NodeReference

View File

@@ -1,7 +1,7 @@
import type { Request, Route } from '@playwright/test'
import _ from 'es-toolkit/compat'
import fs from 'fs'
import path from 'path'
import type { Request, Route } from 'playwright'
import { v4 as uuidv4 } from 'uuid'
import type {

View File

@@ -0,0 +1,131 @@
import type { Locator, Page } from '@playwright/test'
import type { NodeReference } from './litegraphUtils'
/**
* VueNodeFixture provides Vue-specific testing utilities for interacting with
* Vue node components. It bridges the gap between litegraph node references
* and Vue UI components.
*/
export class VueNodeFixture {
constructor(
private readonly nodeRef: NodeReference,
private readonly page: Page
) {}
/**
* Get the node's header element using data-testid
*/
async getHeader(): Promise<Locator> {
const nodeId = this.nodeRef.id
return this.page.locator(`[data-testid="node-header-${nodeId}"]`)
}
/**
* Get the node's title element
*/
async getTitleElement(): Promise<Locator> {
const header = await this.getHeader()
return header.locator('[data-testid="node-title"]')
}
/**
* Get the current title text
*/
async getTitle(): Promise<string> {
const titleElement = await this.getTitleElement()
return (await titleElement.textContent()) || ''
}
/**
* Set a new title by double-clicking and entering text
*/
async setTitle(newTitle: string): Promise<void> {
const titleElement = await this.getTitleElement()
await titleElement.dblclick()
const input = (await this.getHeader()).locator(
'[data-testid="node-title-input"]'
)
await input.fill(newTitle)
await input.press('Enter')
}
/**
* Cancel title editing
*/
async cancelTitleEdit(): Promise<void> {
const titleElement = await this.getTitleElement()
await titleElement.dblclick()
const input = (await this.getHeader()).locator(
'[data-testid="node-title-input"]'
)
await input.press('Escape')
}
/**
* Check if the title is currently being edited
*/
async isEditingTitle(): Promise<boolean> {
const header = await this.getHeader()
const input = header.locator('[data-testid="node-title-input"]')
return await input.isVisible()
}
/**
* Get the collapse/expand button
*/
async getCollapseButton(): Promise<Locator> {
const header = await this.getHeader()
return header.locator('[data-testid="node-collapse-button"]')
}
/**
* Toggle the node's collapsed state
*/
async toggleCollapse(): Promise<void> {
const button = await this.getCollapseButton()
await button.click()
}
/**
* Get the collapse icon element
*/
async getCollapseIcon(): Promise<Locator> {
const button = await this.getCollapseButton()
return button.locator('i')
}
/**
* Get the collapse icon's CSS classes
*/
async getCollapseIconClass(): Promise<string> {
const icon = await this.getCollapseIcon()
return (await icon.getAttribute('class')) || ''
}
/**
* Check if the collapse button is visible
*/
async isCollapseButtonVisible(): Promise<boolean> {
const button = await this.getCollapseButton()
return await button.isVisible()
}
/**
* Get the node's body/content element
*/
async getBody(): Promise<Locator> {
const nodeId = this.nodeRef.id
return this.page.locator(`[data-testid="node-body-${nodeId}"]`)
}
/**
* Check if the node body is visible (not collapsed)
*/
async isBodyVisible(): Promise<boolean> {
const body = await this.getBody()
return await body.isVisible()
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -36,6 +36,10 @@ test('Does not report warning on undo/redo', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('missing/missing_nodes')
await comfyPage.closeDialog()
// Wait for any async operations to complete after dialog closes
await comfyPage.nextFrame()
await comfyPage.page.waitForTimeout(100)
// Make a change to the graph
await comfyPage.doubleClickCanvas()
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
@@ -59,18 +63,6 @@ test.describe('Execution error', () => {
const executionError = comfyPage.page.locator('.comfy-error-report')
await expect(executionError).toBeVisible()
})
test('Can display Issue Report form', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('nodes/execution_error')
await comfyPage.queueButton.click()
await comfyPage.nextFrame()
await comfyPage.page.getByLabel('Help Fix This').click()
const issueReportForm = comfyPage.page.getByText(
'Submit Error Report (Optional)'
)
await expect(issueReportForm).toBeVisible()
})
})
test.describe('Missing models warning', () => {
@@ -303,37 +295,16 @@ test.describe('Settings', () => {
})
})
test.describe('Feedback dialog', () => {
test('Should open from topmenu help command', async ({ comfyPage }) => {
// Open feedback dialog from top menu
test.describe('Support', () => {
test('Should open external zendesk link', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Feedback'])
const pagePromise = comfyPage.page.context().waitForEvent('page')
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Support'])
const newPage = await pagePromise
// Verify feedback dialog content is visible
const feedbackHeader = comfyPage.page.getByRole('heading', {
name: 'Feedback'
})
await expect(feedbackHeader).toBeVisible()
})
test('Should close when close button clicked', async ({ comfyPage }) => {
// Open feedback dialog
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Feedback'])
const feedbackHeader = comfyPage.page.getByRole('heading', {
name: 'Feedback'
})
// Close feedback dialog
await comfyPage.page
.getByLabel('', { exact: true })
.getByLabel('Close')
.click()
await feedbackHeader.waitFor({ state: 'hidden' })
// Verify dialog is closed
await expect(feedbackHeader).not.toBeVisible()
await newPage.waitForLoadState('networkidle')
await expect(newPage).toHaveURL(/.*support\.comfy\.org.*/)
await newPage.close()
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Some files were not shown because too many files have changed in this diff Show More