Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 56 additions & 10 deletions .github/workflows/pr-playwright-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,39 @@ jobs:
fi
echo "branch=${{ fromJSON(steps.pr-info.outputs.result).sanitized_branch }}" >> $GITHUB_OUTPUT

- name: Download playwright report
- name: Install pnpm
if: fromJSON(steps.pr-info.outputs.result).number != null
uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
if: fromJSON(steps.pr-info.outputs.result).number != null
with:
node-version: lts/*

- name: Download playwright report shards
if: fromJSON(steps.pr-info.outputs.result).number != null
uses: actions/download-artifact@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: playwright-report-${{ matrix.browser }}
path: playwright-report
pattern: playwright-report-${{ matrix.browser }}-shard-*
path: playwright-shards

- name: Install Playwright
if: fromJSON(steps.pr-info.outputs.result).number != null
run: npm install @playwright/test

- name: Merge reports
if: fromJSON(steps.pr-info.outputs.result).number != null
run: |
npx playwright merge-reports --reporter html ./playwright-shards
mv playwright-report merged-report

- name: Rename merged report
if: fromJSON(steps.pr-info.outputs.result).number != null
run: mv merged-report playwright-report

- name: Install Wrangler
if: fromJSON(steps.pr-info.outputs.result).number != null
Expand All @@ -78,14 +103,20 @@ jobs:
RETRY_COUNT=0
MAX_RETRIES=3
SUCCESS=false
DEPLOYMENT_URL=""

while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ $SUCCESS = false ]; do
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "Deployment attempt $RETRY_COUNT of $MAX_RETRIES..."

if npx wrangler pages deploy playwright-report --project-name=${{ steps.project-name.outputs.name }} --branch=${{ steps.project-name.outputs.branch }}; then
OUTPUT=$(npx wrangler pages deploy playwright-report --project-name=${{ steps.project-name.outputs.name }} --branch=${{ steps.project-name.outputs.branch }} 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
SUCCESS=true
DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oE 'https://[^ ]+\.pages\.dev' | head -1)
echo "Deployment successful on attempt $RETRY_COUNT"
echo "URL: $DEPLOYMENT_URL"
else
echo "Deployment failed on attempt $RETRY_COUNT"
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
Expand All @@ -99,10 +130,25 @@ jobs:
echo "All deployment attempts failed"
exit 1
fi

echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

- name: Save deployment info
if: fromJSON(steps.pr-info.outputs.result).number != null
run: |
echo "${{ matrix.browser }}|${{ steps.cloudflare-deploy.outcome == 'success' && '0' || '1' }}|${{ steps.cloudflare-deploy.outputs.deployment_url || '#' }}" > deployment-info-${{ matrix.browser }}.txt

- name: Upload deployment info
if: fromJSON(steps.pr-info.outputs.result).number != null
uses: actions/upload-artifact@v4
with:
name: deployment-info-${{ matrix.browser }}
path: deployment-info-${{ matrix.browser }}.txt
retention-days: 1

comment-tests-starting:
runs-on: ubuntu-latest
if: github.repository == 'Comfy-Org/ComfyUI_frontend' && github.event.workflow_run.event == 'pull_request' && github.event.action == 'requested'
Expand Down Expand Up @@ -144,14 +190,14 @@ jobs:
echo "" >> comment.md
echo "⏰ Started at: ${{ steps.completion-time.outputs.time }} UTC" >> comment.md
echo "" >> comment.md
echo "### 🚀 Running Tests" >> comment.md
echo "- 🧪 **chromium**: Running tests..." >> comment.md
echo "- 🧪 **chromium-0.5x**: Running tests..." >> comment.md
echo "- 🧪 **chromium-2x**: Running tests..." >> comment.md
echo "- 🧪 **mobile-chrome**: Running tests..." >> comment.md
echo "### 🚀 Running Tests (5 parallel shards per browser)" >> comment.md
echo "- 🧪 **chromium**: Running tests in 5 shards..." >> comment.md
echo "- 🧪 **chromium-0.5x**: Running tests in 5 shards..." >> comment.md
echo "- 🧪 **chromium-2x**: Running tests in 5 shards..." >> comment.md
echo "- 🧪 **mobile-chrome**: Running tests in 5 shards..." >> comment.md
echo "" >> comment.md
echo "---" >> comment.md
echo "⏱️ Please wait while tests are running across all browsers..." >> comment.md
echo "⏱️ Tests are running in parallel across 20 jobs (4 browsers × 5 shards)..." >> comment.md

- name: Comment PR - Tests Started
if: steps.pr.outputs.result != 'null'
Expand Down
57 changes: 53 additions & 4 deletions .github/workflows/test-ui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,33 @@ jobs:
strategy:
fail-fast: false
matrix:
browser: [chromium, chromium-2x, chromium-0.5x, mobile-chrome]
include:
# Chromium tests with 5 shards (optimized distribution)
- browser: chromium
shard: 1
shard-total: 5
- browser: chromium
shard: 2
shard-total: 5
- browser: chromium
shard: 3
shard-total: 5
- browser: chromium
shard: 4
shard-total: 5
- browser: chromium
shard: 5
shard-total: 5
# Other browser variants without sharding (faster tests)
- browser: chromium-2x
shard: 1
shard-total: 1
- browser: chromium-0.5x
shard: 1
shard-total: 1
- browser: mobile-chrome
shard: 1
shard-total: 1
steps:
- name: Wait for cache propagation
run: sleep 10
Expand Down Expand Up @@ -135,15 +161,38 @@ jobs:
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend

- name: Run Playwright tests (${{ matrix.browser }})
- name: Run Playwright tests (${{ matrix.browser }}${{ matrix.shard-total > 1 && format(', shard {0}/{1}', matrix.shard, matrix.shard-total) || '' }})
id: playwright
run: npx playwright test --project=${{ matrix.browser }} --reporter=html
run: |
if [ "${{ matrix.shard-total }}" -gt 1 ]; then
npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shard }}/${{ matrix.shard-total }}
else
npx playwright test --project=${{ matrix.browser }}
fi
working-directory: ComfyUI_frontend

- uses: actions/upload-artifact@v4
if: always() # note: use always() to allow results to be upload/report even tests failed.
with:
name: playwright-report-${{ matrix.browser }}
name: playwright-report-${{ matrix.browser }}-shard-${{ matrix.shard }}
path: ComfyUI_frontend/playwright-report/
retention-days: 30

merge-reports:
needs: playwright-tests
if: always()
runs-on: ubuntu-latest
steps:
- name: Download all workflow artifacts
uses: actions/download-artifact@v4
with:
pattern: playwright-report-*
path: all-reports/

- name: Upload merged report
uses: actions/upload-artifact@v4
with:
name: playwright-report-merged
path: all-reports/
retention-days: 30

87 changes: 87 additions & 0 deletions browser_tests/SHARDING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Playwright Test Sharding Strategy

## Overview

This document describes the optimized sharding strategy for Playwright tests to achieve balanced execution times across parallel CI jobs.

## Problem

The original naive sharding approach (dividing tests equally by file count) resulted in imbalanced execution times:
- Shard 5 (chromium): 9 minutes
- Other shards: 2-6 minutes

This was due to `interaction.spec.ts` containing 61 tests with 81 screenshot comparisons, making it significantly heavier than other test files.

## Solution

### 1. Weighted Test Distribution

Tests are assigned weights based on:
- Number of test cases
- Screenshot comparisons (heavy operations)
- Test complexity (DOM manipulation, async operations)
- Historical execution time

### 2. Optimized Shard Configuration

The sharding configuration uses a greedy algorithm to distribute tests:
1. Sort tests by weight (heaviest first)
2. Assign each test to the shard with lowest total weight
3. Result: ~4.5% imbalance vs. previous 80% imbalance

### 3. Project-Specific Sharding

- **chromium**: 5 shards with optimized distribution
- **chromium-2x, chromium-0.5x**: No sharding (fast enough)
- **mobile-chrome**: No sharding (fast enough)

## Implementation

### Generated Configuration

Run `pnpm test:browser:optimize-shards` to regenerate the shard configuration based on current test weights.

### Files

- `shardConfig.generated.ts`: Auto-generated shard assignments
- `playwright-sharded.config.ts`: Playwright config using optimized shards
- `scripts/optimizeSharding.js`: Script to analyze and optimize distribution

### CI Configuration

The GitHub workflow uses a matrix strategy with explicit shard configurations:

```yaml
matrix:
include:
- browser: chromium
shard: 1
shard-total: 5
# ... etc
```

## Shard Distribution (Balanced)

| Shard | Weight | Key Tests |
|-------|--------|-----------|
| 1 | 225 | interaction.spec.ts (heavy screenshots) |
| 2 | 220 | subgraph.spec.ts, workflows, primitives |
| 3 | 225 | widget.spec.ts, nodeLibrary, templates |
| 4 | 215 | nodeSearchBox, rightClickMenu, colorPalette |
| 5 | 215 | dialog, groupNode, remoteWidgets |

## Monitoring

After deployment, monitor CI execution times to ensure shards remain balanced. If new heavy tests are added, re-run the optimization script.

## Maintenance

1. When adding new heavy tests, update `TEST_WEIGHTS` in `optimizeSharding.js`
2. Run `pnpm test:browser:optimize-shards`
3. Commit the updated `shardConfig.generated.ts`

## Expected Results

- All chromium shards complete in 3-4 minutes (vs. 2-9 minutes)
- Total CI time reduced from 9 minutes to ~4 minutes
- Better resource utilization in CI runners
73 changes: 73 additions & 0 deletions browser_tests/customTestRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Custom test runner for optimized sharding
* This script determines which tests to run based on shard configuration
*/
import { spawn } from 'child_process'

import { NO_SHARD_PROJECTS, getShardTests } from './shardConfig'

const projectName = process.env.PLAYWRIGHT_PROJECT || 'chromium'
const shardInfo = process.env.PLAYWRIGHT_SHARD

// Parse shard information from environment variable (format: "current/total")
let shardIndex = 1
let totalShards = 1

if (shardInfo) {
const [current, total] = shardInfo.split('/').map(Number)
shardIndex = current
totalShards = total
}

// Check if this project should skip sharding
if (NO_SHARD_PROJECTS.includes(projectName)) {
// For projects that don't need sharding, only run on shard 1
if (shardIndex > 1) {
console.log(
`Skipping shard ${shardIndex}/${totalShards} for project ${projectName} (no sharding needed)`
)
process.exit(0)
}
console.log(`Running all tests for project ${projectName} (no sharding)`)
} else {
console.log(
`Running shard ${shardIndex}/${totalShards} for project ${projectName}`
)
}

// Get the test files for this shard
const shardTests = getShardTests(shardIndex, totalShards, projectName)

// Build the Playwright command
const args = ['playwright', 'test', `--project=${projectName}`]

if (shardTests && shardTests.length > 0) {
// Add specific test files for this shard
shardTests.forEach((testFile) => {
args.push(`browser_tests/tests/${testFile}`)
})
} else if (shardTests === null) {
// Run all tests (no custom sharding)
// Don't add any test file filters
} else {
// Empty shard - no tests to run
console.log(`No tests assigned to shard ${shardIndex}/${totalShards}`)
process.exit(0)
}

// Add CI-specific options if running in CI
if (process.env.CI) {
args.push('--reporter=github')
}

// Execute Playwright with the constructed arguments
console.log(`Executing: npx ${args.join(' ')}`)
const child = spawn('npx', args, {
stdio: 'inherit',
shell: true
})

child.on('exit', (code) => {
process.exit(code || 0)
})
Loading
Loading