Merge main into austin/widgets-v2
@@ -1,30 +1,85 @@
|
|||||||
# Create Hotfix Release
|
# 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>
|
<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
|
Expected format: Comma-separated list of commits or PR numbers
|
||||||
Examples:
|
Examples:
|
||||||
- `abc123,def456,ghi789` (commits)
|
- `#1234,#5678` (PRs - preferred)
|
||||||
- `#1234,#5678` (PRs)
|
- `abc123,def456` (commit hashes)
|
||||||
- `abc123,#1234,def456` (mixed)
|
- `#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>
|
</task>
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Before starting, ensure:
|
- Push access to repository
|
||||||
- You have push access to the repository
|
- GitHub CLI (`gh`) authenticated
|
||||||
- GitHub CLI (`gh`) is authenticated
|
- Clean working tree
|
||||||
- You're on a clean working tree
|
- Understanding of what fixes need backporting
|
||||||
- You understand the commits/PRs you're cherry-picking
|
|
||||||
|
|
||||||
## Hotfix Release Process
|
## 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:
|
1. Fetch the current ComfyUI requirements.txt from master branch:
|
||||||
```bash
|
```bash
|
||||||
@@ -36,7 +91,7 @@ Before starting, ensure:
|
|||||||
5. Verify the core branch exists: `git ls-remote origin refs/heads/core/*`
|
5. Verify the core branch exists: `git ls-remote origin refs/heads/core/*`
|
||||||
6. **CONFIRMATION REQUIRED**: Is `core/X.Y` the correct target branch?
|
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
|
1. Parse the comma-separated list of commits/PRs
|
||||||
2. For each item:
|
2. For each item:
|
||||||
@@ -49,7 +104,7 @@ Before starting, ensure:
|
|||||||
- **CONFIRMATION REQUIRED**: Use merge commit or cherry-pick individual commits?
|
- **CONFIRMATION REQUIRED**: Use merge commit or cherry-pick individual commits?
|
||||||
4. Validate all commit hashes exist in the repository
|
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:
|
1. For each commit/PR to cherry-pick:
|
||||||
- Display commit hash, author, date
|
- Display commit hash, author, date
|
||||||
@@ -60,7 +115,7 @@ Before starting, ensure:
|
|||||||
2. Identify potential conflicts by checking changed files
|
2. Identify potential conflicts by checking changed files
|
||||||
3. **CONFIRMATION REQUIRED**: Proceed with these commits?
|
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`)
|
1. Checkout the core branch (e.g., `core/1.23`)
|
||||||
2. Pull latest changes: `git pull origin core/X.Y`
|
2. Pull latest changes: `git pull origin core/X.Y`
|
||||||
@@ -69,7 +124,7 @@ Before starting, ensure:
|
|||||||
- Example: `hotfix/1.23.4-20241120`
|
- Example: `hotfix/1.23.4-20241120`
|
||||||
5. **CONFIRMATION REQUIRED**: Created branch correctly?
|
5. **CONFIRMATION REQUIRED**: Created branch correctly?
|
||||||
|
|
||||||
### Step 5: Cherry-pick Changes
|
### Step 6: Cherry-pick Changes
|
||||||
|
|
||||||
For each commit:
|
For each commit:
|
||||||
1. Attempt cherry-pick: `git cherry-pick <commit>`
|
1. Attempt cherry-pick: `git cherry-pick <commit>`
|
||||||
@@ -83,7 +138,7 @@ For each commit:
|
|||||||
- Run validation: `pnpm typecheck && pnpm lint`
|
- Run validation: `pnpm typecheck && pnpm lint`
|
||||||
4. **CONFIRMATION REQUIRED**: Cherry-pick successful and valid?
|
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>`
|
1. Push the hotfix branch: `git push origin hotfix/<version>-<timestamp>`
|
||||||
2. Create PR using gh CLI:
|
2. Create PR using gh CLI:
|
||||||
@@ -100,7 +155,7 @@ For each commit:
|
|||||||
- Impact assessment
|
- Impact assessment
|
||||||
5. **CONFIRMATION REQUIRED**: PR created correctly?
|
5. **CONFIRMATION REQUIRED**: PR created correctly?
|
||||||
|
|
||||||
### Step 7: Wait for Tests
|
### Step 8: Wait for Tests
|
||||||
|
|
||||||
1. Monitor PR checks: `gh pr checks`
|
1. Monitor PR checks: `gh pr checks`
|
||||||
2. Display test results as they complete
|
2. Display test results as they complete
|
||||||
@@ -111,7 +166,7 @@ For each commit:
|
|||||||
4. Wait for all required checks to pass
|
4. Wait for all required checks to pass
|
||||||
5. **CONFIRMATION REQUIRED**: All tests passing?
|
5. **CONFIRMATION REQUIRED**: All tests passing?
|
||||||
|
|
||||||
### Step 8: Merge Hotfix PR
|
### Step 9: Merge Hotfix PR
|
||||||
|
|
||||||
1. Verify all checks have passed
|
1. Verify all checks have passed
|
||||||
2. Check for required approvals
|
2. Check for required approvals
|
||||||
@@ -119,7 +174,7 @@ For each commit:
|
|||||||
4. Delete the hotfix branch
|
4. Delete the hotfix branch
|
||||||
5. **CONFIRMATION REQUIRED**: PR merged successfully?
|
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`
|
1. Checkout the core branch: `git checkout core/X.Y`
|
||||||
2. Pull latest changes: `git pull origin 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"`
|
7. Commit: `git commit -m "[release] Bump version to 1.23.5"`
|
||||||
8. **CONFIRMATION REQUIRED**: Version bump correct?
|
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`
|
1. Push release branch: `git push origin release/1.23.5`
|
||||||
2. Create PR with Release label:
|
2. Create PR with Release label:
|
||||||
@@ -184,7 +239,7 @@ For each commit:
|
|||||||
```
|
```
|
||||||
5. **CONFIRMATION REQUIRED**: Release PR has "Release" label?
|
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
|
1. Wait for PR checks to pass
|
||||||
2. **FINAL CONFIRMATION**: Ready to trigger release by merging?
|
2. **FINAL CONFIRMATION**: Ready to trigger release by merging?
|
||||||
@@ -199,7 +254,102 @@ For each commit:
|
|||||||
- PyPI upload
|
- PyPI upload
|
||||||
- pnpm types publication
|
- 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:
|
1. Verify GitHub release:
|
||||||
```bash
|
```bash
|
||||||
@@ -213,12 +363,14 @@ For each commit:
|
|||||||
```bash
|
```bash
|
||||||
pnpm view @comfyorg/comfyui-frontend-types@1.23.5
|
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
|
- Version released
|
||||||
- Commits included
|
- Commits included
|
||||||
- Issues fixed
|
- Issues fixed
|
||||||
- Distribution status
|
- Distribution status
|
||||||
5. **CONFIRMATION REQUIRED**: Release completed successfully?
|
- ComfyUI integration status
|
||||||
|
6. **CONFIRMATION REQUIRED**: Hotfix release fully completed?
|
||||||
|
|
||||||
## Safety Checks
|
## Safety Checks
|
||||||
|
|
||||||
@@ -240,19 +392,28 @@ If something goes wrong:
|
|||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
|
- **Always try automated backports first** - This command is for when automation fails
|
||||||
- Core branch version will be behind main - this is expected
|
- Core branch version will be behind main - this is expected
|
||||||
- The "Release" label triggers the PyPI/npm publication
|
- 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
|
- PR numbers must include the `#` prefix
|
||||||
- Mixed commits/PRs are supported but review carefully
|
- Mixed commits/PRs are supported but review carefully
|
||||||
- Always wait for full test suite before proceeding
|
- Always wait for full test suite before proceeding
|
||||||
|
|
||||||
## Expected Timeline
|
## Modern Workflow Context
|
||||||
|
|
||||||
- Step 1-3: ~10 minutes (analysis)
|
**Primary Backport Method:** Automated via `needs-backport` + `X.YY` labels
|
||||||
- Steps 4-6: ~15-30 minutes (cherry-picking)
|
**This Command Usage:**
|
||||||
- Step 7: ~10-20 minutes (tests)
|
- Smart path detection - skip to version bump if backports already merged
|
||||||
- Steps 8-10: ~10 minutes (version bump)
|
- Fallback to manual cherry-picking only when automation fails/has conflicts
|
||||||
- Step 11-12: ~15-20 minutes (release)
|
**Complete Hotfix:** Includes GitHub release publishing + ComfyUI requirements.txt integration
|
||||||
- Total: ~60-90 minutes
|
|
||||||
|
|
||||||
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.
|
||||||
158
.claude/commands/setup_repo.md
Normal 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)
|
||||||
@@ -33,3 +33,4 @@ DISABLE_VUE_PLUGINS=false
|
|||||||
# Algolia credentials required for developing with the new custom node manager.
|
# Algolia credentials required for developing with the new custom node manager.
|
||||||
ALGOLIA_APP_ID=4E0RO38HS8
|
ALGOLIA_APP_ID=4E0RO38HS8
|
||||||
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579
|
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,6 @@
|
|||||||
|
|
||||||
# npm run format on litegraph merge (10,672 insertions, 7,327 deletions across 129 files)
|
# npm run format on litegraph merge (10,672 insertions, 7,327 deletions across 129 files)
|
||||||
c53f197de2a3e0fa66b16dedc65c131235c1c4b6
|
c53f197de2a3e0fa66b16dedc65c131235c1c4b6
|
||||||
|
|
||||||
|
# Reorganize renderer components into domain-driven folder structure
|
||||||
|
c8a83a9caede7bdb5f8598c5492b07d08c339d49
|
||||||
|
|||||||
1
.gitattributes
vendored
@@ -9,6 +9,7 @@
|
|||||||
*.mts text eol=lf
|
*.mts text eol=lf
|
||||||
*.ts text eol=lf
|
*.ts text eol=lf
|
||||||
*.vue text eol=lf
|
*.vue text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
src/types/comfyRegistryTypes.ts linguist-generated=true
|
src/types/comfyRegistryTypes.ts linguist-generated=true
|
||||||
|
|||||||
27
.github/workflows/backport.yaml
vendored
@@ -2,7 +2,7 @@ name: Auto Backport
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [closed]
|
types: [closed, labeled]
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -25,7 +25,27 @@ jobs:
|
|||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
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
|
- name: Extract version labels
|
||||||
|
if: steps.check-existing.outputs.skip != 'true'
|
||||||
id: versions
|
id: versions
|
||||||
run: |
|
run: |
|
||||||
# Extract version labels (e.g., "1.24", "1.22")
|
# Extract version labels (e.g., "1.24", "1.22")
|
||||||
@@ -52,6 +72,7 @@ jobs:
|
|||||||
echo "Found version labels: ${VERSIONS}"
|
echo "Found version labels: ${VERSIONS}"
|
||||||
|
|
||||||
- name: Backport commits
|
- name: Backport commits
|
||||||
|
if: steps.check-existing.outputs.skip != 'true'
|
||||||
id: backport
|
id: backport
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
@@ -109,7 +130,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create PR for each successful backport
|
- name: Create PR for each successful backport
|
||||||
if: steps.backport.outputs.success
|
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
@@ -141,7 +162,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Comment on failures
|
- name: Comment on failures
|
||||||
if: failure() && steps.backport.outputs.failed
|
if: steps.check-existing.outputs.skip != 'true' && failure() && steps.backport.outputs.failed
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
60
.github/workflows/chromatic.yaml
vendored
@@ -12,9 +12,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Only run for PRs from version-bump-* branches or manual triggers
|
# Only run for PRs from version-bump-* branches or manual triggers
|
||||||
if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'version-bump-')
|
if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'version-bump-')
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -32,29 +29,6 @@ jobs:
|
|||||||
node-version: '20'
|
node-version: '20'
|
||||||
cache: 'pnpm'
|
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
|
- name: Cache tool outputs
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -81,37 +55,3 @@ jobs:
|
|||||||
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
||||||
exitOnceUploaded: true # Don't wait for UI tests to complete
|
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.' }}
|
|
||||||
|
|||||||
9
.github/workflows/claude-pr-review.yml
vendored
@@ -47,6 +47,7 @@ jobs:
|
|||||||
needs: wait-for-ci
|
needs: wait-for-ci
|
||||||
if: needs.wait-for-ci.outputs.should-proceed == 'true'
|
if: needs.wait-for-ci.outputs.should-proceed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -69,19 +70,17 @@ jobs:
|
|||||||
pnpm install -g typescript @vue/compiler-sfc
|
pnpm install -g typescript @vue/compiler-sfc
|
||||||
|
|
||||||
- name: Run Claude PR Review
|
- name: Run Claude PR Review
|
||||||
uses: anthropics/claude-code-action@main
|
uses: anthropics/claude-code-action@v1.0.6
|
||||||
with:
|
with:
|
||||||
label_trigger: "claude-review"
|
label_trigger: "claude-review"
|
||||||
direct_prompt: |
|
prompt: |
|
||||||
Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly.
|
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.
|
CRITICAL: You must post individual inline comments using the gh api commands shown in the file.
|
||||||
DO NOT create a summary comment.
|
DO NOT create a summary comment.
|
||||||
Each issue must be posted as a separate inline comment on the specific line of code.
|
Each issue must be posted as a separate inline comment on the specific line of code.
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
max_turns: 256
|
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'"
|
||||||
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"
|
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -128,45 +128,6 @@ jobs:
|
|||||||
echo "- Critical security patches"
|
echo "- Critical security patches"
|
||||||
echo "- Documentation updates"
|
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
|
- name: Post summary
|
||||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||||
|
|||||||
9
.github/workflows/i18n.yaml
vendored
@@ -25,6 +25,13 @@ jobs:
|
|||||||
key: i18n-tools-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
|
key: i18n-tools-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
i18n-tools-cache-${{ runner.os }}-
|
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
|
- name: Install Playwright Browsers
|
||||||
run: npx playwright install chromium --with-deps
|
run: npx playwright install chromium --with-deps
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
@@ -34,7 +41,7 @@ jobs:
|
|||||||
run: pnpm dev:electron &
|
run: pnpm dev:electron &
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
- name: Update en.json
|
- name: Update en.json
|
||||||
run: pnpm collect-i18n -- scripts/collect-i18n-general.ts
|
run: pnpm collect-i18n
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
|
|||||||
2
.github/workflows/lint-and-format.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
|
|||||||
92
.github/workflows/pr-playwright-deploy.yaml
vendored
Normal 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"
|
||||||
126
.github/workflows/pr-storybook-comment.yaml
vendored
Normal 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.'
|
||||||
|
}}
|
||||||
7
.github/workflows/test-browser-exp.yaml
vendored
@@ -11,6 +11,13 @@ jobs:
|
|||||||
if: github.event.label.name == 'New Browser Test Expectations'
|
if: github.event.label.name == 'New Browser Test Expectations'
|
||||||
steps:
|
steps:
|
||||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
|
- 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
|
- name: Install Playwright Browsers
|
||||||
run: npx playwright install chromium --with-deps
|
run: npx playwright install chromium --with-deps
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
|
|||||||
443
.github/workflows/test-ui.yaml
vendored
@@ -7,15 +7,12 @@ on:
|
|||||||
branches-ignore:
|
branches-ignore:
|
||||||
[wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
|
[wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
|
||||||
|
|
||||||
env:
|
|
||||||
DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||||
sanitized-branch: ${{ steps.branch-info.outputs.sanitized }}
|
playwright-version: ${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ComfyUI
|
- name: Checkout ComfyUI
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -48,30 +45,6 @@ jobs:
|
|||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml'
|
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
|
- name: Cache tool outputs
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -94,13 +67,12 @@ jobs:
|
|||||||
id: cache-key
|
id: cache-key
|
||||||
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
|
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Generate sanitized branch name
|
- name: Playwright Version
|
||||||
id: branch-info
|
id: playwright-version
|
||||||
run: |
|
run: |
|
||||||
# Get branch name and sanitize it for Cloudflare branch names
|
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version')
|
||||||
BRANCH_NAME="${{ github.head_ref || github.ref_name }}"
|
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
|
||||||
SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
|
working-directory: ComfyUI_frontend
|
||||||
echo "sanitized=${SANITIZED_BRANCH}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Save cache
|
- name: Save cache
|
||||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
||||||
@@ -110,17 +82,17 @@ jobs:
|
|||||||
ComfyUI_frontend
|
ComfyUI_frontend
|
||||||
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
|
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
|
||||||
|
|
||||||
playwright-tests:
|
# Sharded chromium tests
|
||||||
|
playwright-tests-chromium-sharded:
|
||||||
needs: setup
|
needs: setup
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
contents: read
|
contents: read
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
browser: [chromium, chromium-2x, chromium-0.5x, mobile-chrome]
|
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||||
|
shardTotal: [8]
|
||||||
steps:
|
steps:
|
||||||
- name: Wait for cache propagation
|
- name: Wait for cache propagation
|
||||||
run: sleep 10
|
run: sleep 10
|
||||||
@@ -144,32 +116,85 @@ jobs:
|
|||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|
||||||
- name: Get current time
|
- name: Install requirements
|
||||||
id: current-time
|
|
||||||
run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Set project name
|
|
||||||
id: project-name
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.browser }}" = "chromium-0.5x" ]; then
|
python -m pip install --upgrade pip
|
||||||
echo "name=comfyui-playwright-chromium-0-5x" >> $GITHUB_OUTPUT
|
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||||
else
|
pip install -r requirements.txt
|
||||||
echo "name=comfyui-playwright-${{ matrix.browser }}" >> $GITHUB_OUTPUT
|
pip install wait-for-it
|
||||||
fi
|
working-directory: ComfyUI
|
||||||
echo "branch=${{ needs.setup.outputs.sanitized-branch }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Comment PR - Browser Test Started
|
|
||||||
if: github.event_name == 'pull_request'
|
- name: Cache Playwright Browsers
|
||||||
continue-on-error: true
|
uses: actions/cache@v4
|
||||||
uses: edumserrano/find-create-or-update-comment@v3
|
id: cache-playwright-browsers
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
path: '~/.cache/ms-playwright'
|
||||||
body-includes: '<!-- PLAYWRIGHT_TEST_STATUS -->'
|
key: '${{ runner.os }}-playwright-browsers-${{ needs.setup.outputs.playwright-version }}'
|
||||||
comment-author: 'github-actions[bot]'
|
|
||||||
edit-mode: append
|
- name: Install Playwright Browsers
|
||||||
body: |
|
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
||||||
<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;" />
|
run: pnpm exec playwright install chromium --with-deps
|
||||||
<bold>${{ matrix.browser }}</bold>: Running tests...
|
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
|
- name: Install requirements
|
||||||
run: |
|
run: |
|
||||||
@@ -179,215 +204,145 @@ jobs:
|
|||||||
pip install wait-for-it
|
pip install wait-for-it
|
||||||
working-directory: ComfyUI
|
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
|
- name: Start ComfyUI server
|
||||||
run: |
|
run: |
|
||||||
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist &
|
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist &
|
||||||
wait-for-it --service 127.0.0.1:8188 -t 600
|
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||||
working-directory: ComfyUI
|
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 }})
|
- name: Run Playwright tests (${{ matrix.browser }})
|
||||||
id: playwright
|
id: playwright
|
||||||
run: npx playwright test --project=${{ matrix.browser }} --reporter=html
|
run: npx playwright test --project=${{ matrix.browser }} --reporter=html
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always() # note: use always() to allow results to be upload/report even tests failed.
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report-${{ matrix.browser }}
|
name: playwright-report-${{ matrix.browser }}
|
||||||
path: ComfyUI_frontend/playwright-report/
|
path: ComfyUI_frontend/playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Pages (${{ matrix.browser }})
|
# Merge sharded test reports
|
||||||
id: cloudflare-deploy
|
merge-reports:
|
||||||
if: always()
|
needs: [playwright-tests-chromium-sharded]
|
||||||
continue-on-error: true
|
runs-on: ubuntu-latest
|
||||||
run: |
|
if: ${{ !cancelled() }}
|
||||||
# Retry logic for wrangler deploy (3 attempts)
|
steps:
|
||||||
RETRY_COUNT=0
|
- name: Checkout ComfyUI_frontend
|
||||||
MAX_RETRIES=3
|
uses: actions/checkout@v4
|
||||||
SUCCESS=false
|
with:
|
||||||
|
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||||
while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ $SUCCESS = false ]; do
|
path: 'ComfyUI_frontend'
|
||||||
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 }}
|
|
||||||
|
|
||||||
- name: Save deployment info for summary
|
- name: Install pnpm
|
||||||
if: always()
|
uses: pnpm/action-setup@v4
|
||||||
run: |
|
with:
|
||||||
mkdir -p deployment-info
|
version: 10
|
||||||
# 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: Upload deployment info
|
- uses: actions/setup-node@v4
|
||||||
if: always()
|
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
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: deployment-info-${{ matrix.browser }}
|
name: playwright-report-chromium
|
||||||
path: deployment-info/
|
path: ComfyUI_frontend/playwright-report/
|
||||||
retention-days: 1
|
retention-days: 30
|
||||||
|
|
||||||
- name: Get completion time
|
#### BEGIN Deployment and commenting (non-forked PRs only)
|
||||||
id: completion-time
|
# when using pull_request event, we have permission to comment directly
|
||||||
if: always()
|
# if its a forked repo, we need to use workflow_run event in a separate workflow (pr-playwright-deploy.yaml)
|
||||||
run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Comment PR - Browser Test Complete
|
# Post starting comment for non-forked PRs
|
||||||
if: always() && github.event_name == 'pull_request'
|
comment-on-pr-start:
|
||||||
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
|
|
||||||
runs-on: ubuntu-latest
|
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:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
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
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: deployment-info-*
|
pattern: playwright-report-*
|
||||||
merge-multiple: true
|
path: reports
|
||||||
path: deployment-info
|
|
||||||
|
- name: Make deployment script executable
|
||||||
- name: Get completion time
|
run: chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
|
||||||
id: completion-time
|
|
||||||
run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT
|
- name: Deploy reports and comment on PR
|
||||||
|
env:
|
||||||
- name: Generate comment body
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
id: comment-body
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
echo "<!-- PLAYWRIGHT_TEST_STATUS -->" > comment.md
|
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||||
echo "## 🎭 Playwright Test Results" >> comment.md
|
"${{ github.event.pull_request.number }}" \
|
||||||
echo "" >> comment.md
|
"${{ github.head_ref }}" \
|
||||||
|
"completed"
|
||||||
# Check if all tests passed
|
#### END Deployment and commenting (non-forked PRs only)
|
||||||
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
|
|
||||||
4
.github/workflows/update-electron-types.yaml
vendored
@@ -35,12 +35,12 @@ jobs:
|
|||||||
electron-types-tools-cache-${{ runner.os }}-
|
electron-types-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Update electron types
|
- 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
|
- name: Get new version
|
||||||
id: get-version
|
id: get-version
|
||||||
run: |
|
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
|
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
|||||||
2
.github/workflows/update-manager-types.yaml
vendored
@@ -121,4 +121,4 @@ jobs:
|
|||||||
labels: Manager
|
labels: Manager
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
add-paths: |
|
add-paths: |
|
||||||
src/types/generatedManagerTypes.ts
|
src/types/generatedManagerTypes.ts
|
||||||
11
.gitignore
vendored
@@ -22,6 +22,7 @@ dist-ssr
|
|||||||
*.local
|
*.local
|
||||||
# Claude configuration
|
# Claude configuration
|
||||||
.claude/*.local.json
|
.claude/*.local.json
|
||||||
|
CLAUDE.local.md
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
@@ -50,6 +51,7 @@ tests-ui/workflows/examples
|
|||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
browser_tests/**/*-win32.png
|
browser_tests/**/*-win32.png
|
||||||
|
browser-tests/local/
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
@@ -76,3 +78,12 @@ vite.config.mts.timestamp-*.mjs
|
|||||||
*storybook.log
|
*storybook.log
|
||||||
storybook-static
|
storybook-static
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.nx/cache
|
||||||
|
.nx/workspace-data
|
||||||
|
.cursor/rules/nx-rules.mdc
|
||||||
|
.github/instructions/nx.instructions.md
|
||||||
|
vite.config.*.timestamp*
|
||||||
|
vitest.config.*.timestamp*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
npx lint-staged
|
pnpm exec lint-staged
|
||||||
npx tsx scripts/check-unused-i18n-keys.ts
|
pnpm exec tsx scripts/check-unused-i18n-keys.ts
|
||||||
|
|||||||
5
.husky/pre-push
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Run Knip with cache via package script
|
||||||
|
pnpm knip
|
||||||
|
|
||||||
12
.mcp.json
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"playwright": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@executeautomation/playwright-mcp-server"]
|
|
||||||
},
|
|
||||||
"context7": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@upstash/context7-mcp"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,17 +4,26 @@
|
|||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light theme default */
|
/* Light theme default - with explicit color to override media queries */
|
||||||
body {
|
body:not(.dark-theme) {
|
||||||
background-color: #ffffff;
|
background-color: #fff !important;
|
||||||
color: #1a1a1a;
|
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 */
|
/* Dark theme styles */
|
||||||
body.dark-theme,
|
body.dark-theme,
|
||||||
.dark-theme body {
|
.dark-theme body {
|
||||||
background-color: #0a0a0a;
|
background-color: #202020;
|
||||||
color: #e5e5e5;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure Storybook canvas follows theme */
|
/* Ensure Storybook canvas follows theme */
|
||||||
@@ -24,11 +33,32 @@
|
|||||||
|
|
||||||
.dark-theme .sb-show-main,
|
.dark-theme .sb-show-main,
|
||||||
.dark-theme .docs-story {
|
.dark-theme .docs-story {
|
||||||
background-color: #0a0a0a !important;
|
background-color: #202020 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix for Storybook controls panel in dark mode */
|
/* CSS Variables for theme consistency */
|
||||||
.dark-theme .docblock-argstable-body {
|
body:not(.dark-theme) {
|
||||||
color: #e5e5e5;
|
--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>
|
</style>
|
||||||
44
.vscode/tailwind.json
vendored
@@ -2,12 +2,32 @@
|
|||||||
"version": 1.1,
|
"version": 1.1,
|
||||||
"atDirectives": [
|
"atDirectives": [
|
||||||
{
|
{
|
||||||
"name": "@tailwind",
|
"name": "@import",
|
||||||
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
|
"description": "Use the `@import` directive to inline CSS files, including Tailwind itself, into your stylesheet.",
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"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",
|
"name": "@config",
|
||||||
"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",
|
"description": "Use the `@config` directive to load a legacy JavaScript-based Tailwind configuration file.",
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#config"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@screen",
|
"name": "@reference",
|
||||||
"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",
|
"description": "Use the `@reference` directive to import theme variables, custom utilities, and custom variants from other files without duplicating CSS.",
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#reference"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@variants",
|
"name": "@plugin",
|
||||||
"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",
|
"description": "Use the `@plugin` directive to load a legacy JavaScript-based Tailwind plugin.",
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#plugin"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
83
CLAUDE.md
@@ -1,22 +1,52 @@
|
|||||||
# ComfyUI Frontend Project Guidelines
|
# 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
|
## Quick Commands
|
||||||
|
|
||||||
- `pnpm`: See all available commands
|
- `pnpm`: See all available commands
|
||||||
|
- `pnpm dev`: Start development server (port 5173, via nx)
|
||||||
- `pnpm typecheck`: Type checking
|
- `pnpm typecheck`: Type checking
|
||||||
- `pnpm lint`: Linting
|
- `pnpm build`: Build for production (via nx)
|
||||||
|
- `pnpm lint`: Linting (via nx)
|
||||||
- `pnpm format`: Prettier formatting
|
- `pnpm format`: Prettier formatting
|
||||||
- `pnpm test:component`: Run component tests with browser environment
|
- `pnpm test:component`: Run component tests with browser environment
|
||||||
- `pnpm test:unit`: Run all unit tests
|
- `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 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
|
## Development Workflow
|
||||||
|
|
||||||
1. Make code changes
|
1. **First-time setup**: Run `/setup_repo` Claude command
|
||||||
2. Run tests (see subdirectory CLAUDE.md files)
|
2. Make code changes
|
||||||
3. Run typecheck, lint, format
|
3. Run tests (see subdirectory CLAUDE.md files)
|
||||||
4. Check README updates
|
4. Run typecheck, lint, format
|
||||||
5. Consider docs.comfy.org updates
|
5. Check README updates
|
||||||
|
6. Consider docs.comfy.org updates
|
||||||
|
|
||||||
## Git Conventions
|
## Git Conventions
|
||||||
|
|
||||||
@@ -52,6 +82,44 @@ When referencing Comfy-Org repos:
|
|||||||
2. Use GitHub API for branches/PRs/metadata
|
2. Use GitHub API for branches/PRs/metadata
|
||||||
3. Curl GitHub website if needed
|
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
|
## Common Pitfalls
|
||||||
|
|
||||||
- NEVER use `any` type - use proper TypeScript types
|
- 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 use `--no-verify` flag when committing
|
||||||
- NEVER delete or disable tests to make them pass
|
- NEVER delete or disable tests to make them pass
|
||||||
- NEVER circumvent quality checks
|
- 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 })" />`
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
|
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
|
||||||
|
|
||||||
# Mask Editor extension
|
# 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
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Have another idea? Drop into Discord or open an issue, and let's chat!
|
|||||||
### Prerequisites & Technology Stack
|
### Prerequisites & Technology Stack
|
||||||
|
|
||||||
- **Required Software**:
|
- **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
|
- Git for version control
|
||||||
- A running ComfyUI backend instance
|
- A running ComfyUI backend instance
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { Position } from './types'
|
|||||||
* - {@link Mouse.move}
|
* - {@link Mouse.move}
|
||||||
* - {@link Mouse.up}
|
* - {@link Mouse.up}
|
||||||
*/
|
*/
|
||||||
export interface DragOptions {
|
interface DragOptions {
|
||||||
button?: 'left' | 'right' | 'middle'
|
button?: 'left' | 'right' | 'middle'
|
||||||
clickCount?: number
|
clickCount?: number
|
||||||
steps?: number
|
steps?: number
|
||||||
|
|||||||
@@ -453,6 +453,32 @@ export class ComfyPage {
|
|||||||
await workflowsTab.close()
|
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() {
|
async resetView() {
|
||||||
if (await this.resetViewButton.isVisible()) {
|
if (await this.resetViewButton.isVisible()) {
|
||||||
await this.resetViewButton.click()
|
await this.resetViewButton.click()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { test as base } from '@playwright/test'
|
import { Page, test as base } from '@playwright/test'
|
||||||
import { Page } from 'playwright'
|
|
||||||
|
|
||||||
export class UserSelectPage {
|
export class UserSelectPage {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { Locator, Page } from '@playwright/test'
|
import { Locator, Page, expect } from '@playwright/test'
|
||||||
|
|
||||||
export class Topbar {
|
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[]> {
|
async getTabNames(): Promise<string[]> {
|
||||||
return await this.page
|
return await this.page
|
||||||
@@ -15,10 +21,33 @@ export class Topbar {
|
|||||||
.innerText()
|
.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}")`)
|
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 {
|
getWorkflowTab(tabName: string): Locator {
|
||||||
return this.page
|
return this.page
|
||||||
.locator(`.workflow-tabs .workflow-label:has-text("${tabName}")`)
|
.locator(`.workflow-tabs .workflow-label:has-text("${tabName}")`)
|
||||||
@@ -66,10 +95,50 @@ export class Topbar {
|
|||||||
|
|
||||||
async openTopbarMenu() {
|
async openTopbarMenu() {
|
||||||
await this.page.waitForTimeout(1000)
|
await this.page.waitForTimeout(1000)
|
||||||
await this.page.locator('.comfyui-logo-wrapper').click()
|
await this.menuTrigger.click()
|
||||||
const menu = this.page.locator('.comfy-command-menu')
|
await this.menuLocator.waitFor({ state: 'visible' })
|
||||||
await menu.waitFor({ state: 'visible' })
|
return this.menuLocator
|
||||||
return menu
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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[]) {
|
async triggerTopbarCommand(path: string[]) {
|
||||||
@@ -79,9 +148,7 @@ export class Topbar {
|
|||||||
|
|
||||||
const menu = await this.openTopbarMenu()
|
const menu = await this.openTopbarMenu()
|
||||||
const tabName = path[0]
|
const tabName = path[0]
|
||||||
const topLevelMenuItem = this.page.locator(
|
const topLevelMenuItem = this.getMenuItem(tabName)
|
||||||
`.p-menubar-item-label:text-is("${tabName}")`
|
|
||||||
)
|
|
||||||
const topLevelMenu = menu
|
const topLevelMenu = menu
|
||||||
.locator('.p-tieredmenu-item')
|
.locator('.p-tieredmenu-item')
|
||||||
.filter({ has: topLevelMenuItem })
|
.filter({ has: topLevelMenuItem })
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export class SubgraphSlotReference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NodeSlotReference {
|
class NodeSlotReference {
|
||||||
constructor(
|
constructor(
|
||||||
readonly type: 'input' | 'output',
|
readonly type: 'input' | 'output',
|
||||||
readonly index: number,
|
readonly index: number,
|
||||||
@@ -201,7 +201,7 @@ export class NodeSlotReference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NodeWidgetReference {
|
class NodeWidgetReference {
|
||||||
constructor(
|
constructor(
|
||||||
readonly index: number,
|
readonly index: number,
|
||||||
readonly node: NodeReference
|
readonly node: NodeReference
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import type { Request, Route } from '@playwright/test'
|
||||||
import _ from 'es-toolkit/compat'
|
import _ from 'es-toolkit/compat'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import type { Request, Route } from 'playwright'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
131
browser_tests/fixtures/utils/vueNodeFixtures.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
@@ -36,6 +36,10 @@ test('Does not report warning on undo/redo', async ({ comfyPage }) => {
|
|||||||
await comfyPage.loadWorkflow('missing/missing_nodes')
|
await comfyPage.loadWorkflow('missing/missing_nodes')
|
||||||
await comfyPage.closeDialog()
|
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
|
// Make a change to the graph
|
||||||
await comfyPage.doubleClickCanvas()
|
await comfyPage.doubleClickCanvas()
|
||||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||||
@@ -59,18 +63,6 @@ test.describe('Execution error', () => {
|
|||||||
const executionError = comfyPage.page.locator('.comfy-error-report')
|
const executionError = comfyPage.page.locator('.comfy-error-report')
|
||||||
await expect(executionError).toBeVisible()
|
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', () => {
|
test.describe('Missing models warning', () => {
|
||||||
@@ -303,37 +295,16 @@ test.describe('Settings', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Feedback dialog', () => {
|
test.describe('Support', () => {
|
||||||
test('Should open from topmenu help command', async ({ comfyPage }) => {
|
test('Should open external zendesk link', async ({ comfyPage }) => {
|
||||||
// Open feedback dialog from top menu
|
|
||||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
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
|
await newPage.waitForLoadState('networkidle')
|
||||||
const feedbackHeader = comfyPage.page.getByRole('heading', {
|
await expect(newPage).toHaveURL(/.*support\.comfy\.org.*/)
|
||||||
name: 'Feedback'
|
await newPage.close()
|
||||||
})
|
|
||||||
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()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |