diff --git a/.github/workflows/ci-tests-e2e-forks.yaml b/.github/workflows/ci-tests-e2e-forks.yaml index c1828b7fb..8f039f1c4 100644 --- a/.github/workflows/ci-tests-e2e-forks.yaml +++ b/.github/workflows/ci-tests-e2e-forks.yaml @@ -1,9 +1,9 @@ # Description: Deploys test results from forked PRs (forks can't access deployment secrets) -name: "CI: Tests E2E (Deploy for Forks)" +name: 'CI: Tests E2E (Deploy for Forks)' on: workflow_run: - workflows: ["CI: Tests E2E"] + workflows: ['CI: Tests E2E'] types: [requested, completed] env: @@ -81,6 +81,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} GITHUB_TOKEN: ${{ github.token }} + GITHUB_SHA: ${{ github.event.workflow_run.head_sha }} run: | # Rename merged report if exists [ -d "reports/playwright-report-chromium-merged" ] && \ diff --git a/.github/workflows/ci-tests-e2e.yaml b/.github/workflows/ci-tests-e2e.yaml index 12ccfae93..c1e0af411 100644 --- a/.github/workflows/ci-tests-e2e.yaml +++ b/.github/workflows/ci-tests-e2e.yaml @@ -1,5 +1,5 @@ # Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages -name: "CI: Tests E2E" +name: 'CI: Tests E2E' on: push: @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 container: - image: ghcr.io/comfy-org/comfyui-ci-container:0.0.8 + image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10 credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} @@ -85,7 +85,7 @@ jobs: needs: setup runs-on: ubuntu-latest container: - image: ghcr.io/comfy-org/comfyui-ci-container:0.0.8 + image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10 credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} @@ -144,9 +144,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v5 - # Setup pnpm/node to run playwright merge-reports (no browsers needed) - - name: Setup frontend - uses: ./.github/actions/setup-frontend + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 - name: Download blob reports uses: actions/download-artifact@v4 @@ -158,10 +159,10 @@ jobs: - name: Merge into HTML Report run: | # Generate HTML report - pnpm exec playwright merge-reports --reporter=html ./all-blob-reports + pnpm dlx @playwright/test merge-reports --reporter=html ./all-blob-reports # Generate JSON report separately with explicit output path PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \ - pnpm exec playwright merge-reports --reporter=json ./all-blob-reports + pnpm dlx @playwright/test merge-reports --reporter=json ./all-blob-reports - name: Upload HTML report uses: actions/upload-artifact@v4 @@ -222,6 +223,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} GITHUB_TOKEN: ${{ github.token }} + GITHUB_SHA: ${{ github.event.pull_request.head.sha }} run: | bash ./scripts/cicd/pr-playwright-deploy-and-comment.sh \ "${{ github.event.pull_request.number }}" \ diff --git a/.github/workflows/pr-update-playwright-expectations.yaml b/.github/workflows/pr-update-playwright-expectations.yaml index e1c51c546..0ca696721 100644 --- a/.github/workflows/pr-update-playwright-expectations.yaml +++ b/.github/workflows/pr-update-playwright-expectations.yaml @@ -77,7 +77,7 @@ jobs: needs: setup runs-on: ubuntu-latest container: - image: ghcr.io/comfy-org/comfyui-ci-container:0.0.8 + image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10 credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-biweekly-comfyui.yaml b/.github/workflows/release-biweekly-comfyui.yaml index c0f57822b..6eb45a00e 100644 --- a/.github/workflows/release-biweekly-comfyui.yaml +++ b/.github/workflows/release-biweekly-comfyui.yaml @@ -69,7 +69,7 @@ jobs: - name: Checkout ComfyUI (sparse) uses: actions/checkout@v5 with: - repository: comfyanonymous/ComfyUI + repository: Comfy-Org/ComfyUI sparse-checkout: | requirements.txt path: comfyui @@ -184,7 +184,7 @@ jobs: # Note: This only affects the local checkout, NOT the fork's master branch # We only push the automation branch, leaving the fork's master untouched echo "Fetching upstream master..." - if ! git fetch https://github.com/comfyanonymous/ComfyUI.git master; then + if ! git fetch https://github.com/Comfy-Org/ComfyUI.git master; then echo "Failed to fetch upstream master" exit 1 fi @@ -257,7 +257,7 @@ jobs: # Extract fork owner from repository name FORK_OWNER=$(echo "$COMFYUI_FORK" | cut -d'/' -f1) - echo "Creating PR from ${COMFYUI_FORK} to comfyanonymous/ComfyUI" + echo "Creating PR from ${COMFYUI_FORK} to Comfy-Org/ComfyUI" # Configure git git config user.name "github-actions[bot]" @@ -288,7 +288,7 @@ jobs: # Try to create PR, ignore error if it already exists if ! gh pr create \ - --repo comfyanonymous/ComfyUI \ + --repo Comfy-Org/ComfyUI \ --head "${FORK_OWNER}:${BRANCH}" \ --base master \ --title "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" \ @@ -297,7 +297,7 @@ jobs: # Check if PR already exists set +e - EXISTING_PR=$(gh pr list --repo comfyanonymous/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1) + EXISTING_PR=$(gh pr list --repo Comfy-Org/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1) PR_LIST_EXIT=$? set -e @@ -318,7 +318,7 @@ jobs: run: | echo "## ComfyUI PR Created" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Draft PR created in comfyanonymous/ComfyUI" >> $GITHUB_STEP_SUMMARY + echo "Draft PR created in Comfy-Org/ComfyUI" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### PR Body:" >> $GITHUB_STEP_SUMMARY cat pr-body.txt >> $GITHUB_STEP_SUMMARY diff --git a/.i18nrc.cjs b/.i18nrc.cjs index f0ed79099..86ce06eaa 100644 --- a/.i18nrc.cjs +++ b/.i18nrc.cjs @@ -10,7 +10,7 @@ module.exports = defineConfig({ entry: 'src/locales/en', entryLocale: 'en', output: 'src/locales', - outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR'], + outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR', 'fa'], reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream, Civitai, Hugging Face. 'latent' is the short form of 'latent space'. 'mask' is in the context of image processing. @@ -19,5 +19,11 @@ module.exports = defineConfig({ - For 'zh' locale: Use ONLY Simplified Chinese characters (简体中文). Common examples: 节点 (not 節點), 画布 (not 畫布), 图像 (not 圖像), 选择 (not 選擇), 减小 (not 減小). - For 'zh-TW' locale: Use ONLY Traditional Chinese characters (繁體中文) with Taiwan-specific terminology. - NEVER mix Simplified and Traditional Chinese characters within the same locale. + + IMPORTANT Persian Translation Guidelines: + - For 'fa' locale: Use formal Persian (فارسی رسمی) for professional tone throughout the UI. + - Keep commonly used technical terms in English when they are standard in Persian software (e.g., node, workflow). + - Use Arabic-Indic numerals (۰-۹) for numbers where appropriate. + - Maintain consistency with terminology used in Persian software and design applications. ` }); diff --git a/.storybook/main.ts b/.storybook/main.ts index af23c6db3..5b7c126e9 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -69,9 +69,32 @@ const config: StorybookConfig = { allowedHosts: true }, resolve: { - alias: { - '@': process.cwd() + '/src' - } + alias: [ + { + find: '@/composables/queue/useJobList', + replacement: process.cwd() + '/src/storybook/mocks/useJobList.ts' + }, + { + find: '@/composables/queue/useJobActions', + replacement: process.cwd() + '/src/storybook/mocks/useJobActions.ts' + }, + { + find: '@/utils/formatUtil', + replacement: + process.cwd() + + '/packages/shared-frontend-utils/src/formatUtil.ts' + }, + { + find: '@/utils/networkUtil', + replacement: + process.cwd() + + '/packages/shared-frontend-utils/src/networkUtil.ts' + }, + { + find: '@', + replacement: process.cwd() + '/src' + } + ] }, esbuild: { // Prevent minification of identifiers to preserve _sfc_main diff --git a/CODEOWNERS b/CODEOWNERS index fcba1e400..2612e3f22 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,7 +37,7 @@ /src/components/graph/selectionToolbox/ @Myestery # Minimap -/src/renderer/extensions/minimap/ @jtydhr88 @Myestery +/src/renderer/extensions/minimap/ @jtydhr88 @Myestery # Workflow Templates /src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki @@ -55,8 +55,7 @@ /src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata # Translations -/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs -/src/locales/pt-BR/ @JonatanAtila @Yorha4D @KarryCharon @shinshin86 +/src/locales/ @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs # LLM Instructions (blank on purpose) .claude/ diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index 08cdd5cce..2f51533ce 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -3,7 +3,7 @@ import { test as base, expect } from '@playwright/test' import dotenv from 'dotenv' import * as fs from 'fs' -import type { LGraphNode } from '../../src/lib/litegraph/src/litegraph' +import type { LGraphNode, LGraph } from '../../src/lib/litegraph/src/litegraph' import type { NodeId } from '../../src/platform/workflow/validation/schemas/workflowSchema' import type { KeyCombo } from '../../src/schemas/keyBindingSchema' import type { useWorkspaceStore } from '../../src/stores/workspaceStore' @@ -1591,14 +1591,29 @@ export class ComfyPage { return window['app'].graph.nodes }) } - async getNodeRefsByType(type: string): Promise { + async waitForGraphNodes(count: number) { + await this.page.waitForFunction((count) => { + return window['app']?.canvas.graph?.nodes?.length === count + }, count) + } + async getNodeRefsByType( + type: string, + includeSubgraph: boolean = false + ): Promise { return Promise.all( ( - await this.page.evaluate((type) => { - return window['app'].graph.nodes - .filter((n: LGraphNode) => n.type === type) - .map((n: LGraphNode) => n.id) - }, type) + await this.page.evaluate( + ({ type, includeSubgraph }) => { + const graph = ( + includeSubgraph ? window['app'].canvas.graph : window['app'].graph + ) as LGraph + const nodes = graph.nodes + return nodes + .filter((n: LGraphNode) => n.type === type) + .map((n: LGraphNode) => n.id) + }, + { type, includeSubgraph } + ) ).map((id: NodeId) => this.getNodeRefById(id)) ) } diff --git a/browser_tests/fixtures/VueNodeHelpers.ts b/browser_tests/fixtures/VueNodeHelpers.ts index e08b39bd7..3c11cfda2 100644 --- a/browser_tests/fixtures/VueNodeHelpers.ts +++ b/browser_tests/fixtures/VueNodeHelpers.ts @@ -159,8 +159,18 @@ export class VueNodeHelpers { getInputNumberControls(widget: Locator) { return { input: widget.locator('input'), - incrementButton: widget.locator('button').first(), - decrementButton: widget.locator('button').nth(1) + decrementButton: widget.getByTestId('decrement'), + incrementButton: widget.getByTestId('increment') } } + + /** + * Enter the subgraph of a node. + * @param nodeId - The ID of the node to enter the subgraph of. If not provided, the first matched subgraph will be entered. + */ + async enterSubgraph(nodeId?: string): Promise { + const locator = nodeId ? this.getNodeLocator(nodeId) : this.page + const editButton = locator.getByTestId('subgraph-enter-button') + await editButton.click() + } } diff --git a/browser_tests/tests/menu.spec.ts b/browser_tests/tests/menu.spec.ts index 2c3ffb3d4..1ba1e5524 100644 --- a/browser_tests/tests/menu.spec.ts +++ b/browser_tests/tests/menu.spec.ts @@ -133,8 +133,11 @@ test.describe('Menu', () => { // Checkmark should be invisible again (panel is hidden) await expect(checkmark).toHaveClass(/invisible/) - // Click outside to close menu - await comfyPage.page.locator('body').click({ position: { x: 10, y: 10 } }) + // Click in top-right corner to close menu (avoid hamburger menu at top-left) + const viewport = comfyPage.page.viewportSize()! + await comfyPage.page + .locator('body') + .click({ position: { x: viewport.width - 10, y: 10 } }) // Verify menu is now closed await expect(menu).not.toBeVisible() diff --git a/browser_tests/tests/mobileBaseline.spec.ts b/browser_tests/tests/mobileBaseline.spec.ts index 21be3ca94..ff766a434 100644 --- a/browser_tests/tests/mobileBaseline.spec.ts +++ b/browser_tests/tests/mobileBaseline.spec.ts @@ -22,8 +22,14 @@ test.describe('Mobile Baseline Snapshots', () => { test('@mobile settings dialog', async ({ comfyPage }) => { await comfyPage.settingDialog.open() await comfyPage.nextFrame() + await expect(comfyPage.settingDialog.root).toHaveScreenshot( - 'mobile-settings-dialog.png' + 'mobile-settings-dialog.png', + { + mask: [ + comfyPage.settingDialog.root.getByTestId('current-user-indicator') + ] + } ) }) }) diff --git a/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-default-workflow-mobile-chrome-linux.png b/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-default-workflow-mobile-chrome-linux.png index b24b68729..b983d8214 100644 Binary files a/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-default-workflow-mobile-chrome-linux.png and b/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-default-workflow-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-empty-canvas-mobile-chrome-linux.png b/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-empty-canvas-mobile-chrome-linux.png index db09fd07a..886d3404c 100644 Binary files a/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-empty-canvas-mobile-chrome-linux.png and b/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-empty-canvas-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-settings-dialog-mobile-chrome-linux.png b/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-settings-dialog-mobile-chrome-linux.png index c179d1c75..d6c21bb98 100644 Binary files a/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-settings-dialog-mobile-chrome-linux.png and b/browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-settings-dialog-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts b/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts index 9ff32c8a4..ff452b54a 100644 --- a/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts +++ b/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts @@ -8,13 +8,11 @@ test.describe('Properties panel', () => { const { propertiesPanel } = comfyPage.menu - await expect(propertiesPanel.panelTitle).toContainText( - 'No node(s) selected' - ) + await expect(propertiesPanel.panelTitle).toContainText('Workflow Overview') await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)']) - await expect(propertiesPanel.panelTitle).toContainText('3 nodes selected') + await expect(propertiesPanel.panelTitle).toContainText('3 items selected') await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1) await expect( propertiesPanel.root.getByText('CLIP Text Encode (Prompt)') diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png index 07ff7a096..885e46356 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png index 7ecb4123e..39e3ade03 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png index 07ff7a096..885e46356 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png differ diff --git a/browser_tests/tests/viewport.spec.ts-snapshots/viewport-fits-when-saved-offscreen-chromium-linux.png b/browser_tests/tests/viewport.spec.ts-snapshots/viewport-fits-when-saved-offscreen-chromium-linux.png index cab519db7..694f33c40 100644 Binary files a/browser_tests/tests/viewport.spec.ts-snapshots/viewport-fits-when-saved-offscreen-chromium-linux.png and b/browser_tests/tests/viewport.spec.ts-snapshots/viewport-fits-when-saved-offscreen-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index 8b66d2c4a..f11f5c2c0 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index 076371135..d00dcf630 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png index 151ee4a48..7bfad824d 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png index 2141828f0..866ef2172 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts index 09a78fb6e..315758f5c 100644 --- a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts @@ -102,7 +102,7 @@ test.describe('Vue Node Link Interaction', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) - await comfyPage.setup() + // await comfyPage.setup() await comfyPage.loadWorkflow('vueNodes/simple-triple') await comfyPage.vueNodes.waitForNodes() await fitToViewInstant(comfyPage) @@ -993,4 +993,51 @@ test.describe('Vue Node Link Interaction', () => { expect(linked).toBe(true) }) }) + + test('Dragging from subgraph input connects to correct slot', async ({ + comfyPage, + comfyMouse + }) => { + // Setup workflow with a KSampler node + await comfyPage.executeCommand('Comfy.NewBlankWorkflow') + await comfyPage.waitForGraphNodes(0) + await comfyPage.executeCommand('Workspace.SearchBox.Toggle') + await comfyPage.nextFrame() + await comfyPage.searchBox.fillAndSelectFirstNode('KSampler') + await comfyPage.waitForGraphNodes(1) + + // Convert the KSampler node to a subgraph + let ksamplerNode = (await comfyPage.getNodeRefsByType('KSampler'))?.[0] + await comfyPage.vueNodes.selectNode(String(ksamplerNode.id)) + await comfyPage.executeCommand('Comfy.Graph.ConvertToSubgraph') + + // Enter the subgraph + await comfyPage.vueNodes.enterSubgraph() + await fitToViewInstant(comfyPage) + + // Get the KSampler node inside the subgraph + ksamplerNode = (await comfyPage.getNodeRefsByType('KSampler', true))?.[0] + const positiveInput = await ksamplerNode.getInput(1) + const negativeInput = await ksamplerNode.getInput(2) + + const positiveInputPos = await getSlotCenter( + comfyPage.page, + ksamplerNode.id, + 1, + true + ) + + const sourceSlot = await comfyPage.getSubgraphInputSlot() + const calculatedSourcePos = await sourceSlot.getOpenSlotPosition() + + await comfyMouse.move(calculatedSourcePos) + await comfyMouse.drag(positiveInputPos) + await comfyMouse.drop() + + // Verify connection went to the correct slot + const positiveLinks = await positiveInput.getLinkCount() + const negativeLinks = await negativeInput.getLinkCount() + expect(positiveLinks).toBe(1) + expect(negativeLinks).toBe(0) + }) }) diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index 5d1cd3684..a846faf0b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index 6485cbec7..ff0eae0d7 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index 8aad10ee6..d7592a0f7 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 10bfe2669..708d21f68 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index 09a9eb864..77971fa12 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index 6ee2d8707..1252ea723 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png index d71dccc7b..667b7c139 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png index 53980f0b9..efcb9ff07 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-after-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-after-chromium-linux.png index 783f0dc0c..8ccc65f98 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-after-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-after-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-before-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-before-chromium-linux.png index 8610dab8b..434c540e1 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-before-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-overlapped-before-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-after-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-after-chromium-linux.png index c4d5979d0..6dc2d6637 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-after-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-after-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-before-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-before-chromium-linux.png index d43f10dde..1fec6b259 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-before-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts-snapshots/bring-to-front-widget-overlapped-before-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png index 9c442e5f5..6b28c14bc 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png index 8562ca7e7..447238891 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png index bbaee4063..b7e4c6e8b 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index 809061b1d..a2823e2ba 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index e6ea9ad30..e0f627695 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index f822ef7a8..a7ebfdac8 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png index 8bd81cbd9..a50a6c88f 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index 6c8966f04..0af0e7447 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/browser_tests/tests/widget.spec.ts b/browser_tests/tests/widget.spec.ts index c8448f36c..075854860 100644 --- a/browser_tests/tests/widget.spec.ts +++ b/browser_tests/tests/widget.spec.ts @@ -194,7 +194,10 @@ test.describe('Image widget', () => { const comboEntry = comfyPage.page.getByRole('menuitem', { name: 'image32x32.webp' }) - await comboEntry.click({ noWaitAfter: true }) + await comboEntry.click() + + // Stabilization for the image swap + await comfyPage.nextFrame() // Expect the image preview to change automatically await expect(comfyPage.canvas).toHaveScreenshot( diff --git a/browser_tests/tests/widget.spec.ts-snapshots/image-preview-changed-by-combo-value-chromium-linux.png b/browser_tests/tests/widget.spec.ts-snapshots/image-preview-changed-by-combo-value-chromium-linux.png index 9c5bffa2d..224d671df 100644 Binary files a/browser_tests/tests/widget.spec.ts-snapshots/image-preview-changed-by-combo-value-chromium-linux.png and b/browser_tests/tests/widget.spec.ts-snapshots/image-preview-changed-by-combo-value-chromium-linux.png differ diff --git a/knip.config.ts b/knip.config.ts index f2907a266..d6a4a7517 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -8,7 +8,8 @@ const config: KnipConfig = { 'src/assets/css/style.css', 'src/main.ts', 'src/scripts/ui/menu/index.ts', - 'src/types/index.ts' + 'src/types/index.ts', + 'src/storybook/mocks/**/*.ts' ], project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}'] }, diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs new file mode 100644 index 000000000..97d22c529 --- /dev/null +++ b/lint-staged.config.mjs @@ -0,0 +1,23 @@ +import path from 'node:path' + +export default { + './**/*.js': (stagedFiles) => formatAndEslint(stagedFiles), + + './**/*.{ts,tsx,vue,mts}': (stagedFiles) => [ + ...formatAndEslint(stagedFiles), + 'pnpm typecheck' + ] +} + +function formatAndEslint(fileNames) { + // Convert absolute paths to relative paths for better ESLint resolution + const relativePaths = fileNames.map((f) => path.relative(process.cwd(), f)) + const joinedPaths = relativePaths.map((p) => `"${p}"`).join(' ') + return [ + `pnpm exec prettier --cache --write ${joinedPaths}`, + `pnpm exec oxlint --fix ${joinedPaths}`, + `pnpm exec eslint --cache --fix --no-warn-ignored ${joinedPaths}` + ] +} + + diff --git a/manifest.json b/manifest.json index d906b2e23..ad419b437 100644 --- a/manifest.json +++ b/manifest.json @@ -3,6 +3,13 @@ "short_name": "ComfyUI", "description": "ComfyUI: AI image generation platform", "start_url": "/", + "icons": [ + { + "src": "/assets/images/comfy-logo-single.svg", + "sizes": "any", + "type": "image/svg+xml" + } + ], "display": "standalone", "background_color": "#ffffff", "theme_color": "#000000" diff --git a/package.json b/package.json index 5f9458d5a..a2105ad9a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.37.8", + "version": "1.38.2", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index 551e26c62..1fdf23af1 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -247,6 +247,7 @@ --inverted-background-hover: var(--color-charcoal-600); --warning-background: var(--color-gold-400); --warning-background-hover: var(--color-gold-500); + --success-background: var(--color-jade-600); --border-default: var(--color-smoke-600); --border-subtle: var(--color-smoke-400); --muted-background: var(--color-smoke-700); @@ -281,7 +282,7 @@ --modal-card-border-highlighted: var(--secondary-background-selected); --modal-card-button-surface: var(--color-smoke-300); --modal-card-placeholder-background: var(--color-smoke-600); - --modal-card-tag-background: var(--color-smoke-400); + --modal-card-tag-background: var(--color-smoke-200); --modal-card-tag-foreground: var(--base-foreground); --modal-panel-background: var(--color-white); } @@ -372,6 +373,7 @@ --inverted-background-hover: var(--color-smoke-200); --warning-background: var(--color-gold-600); --warning-background-hover: var(--color-gold-500); + --success-background: var(--color-jade-600); --border-default: var(--color-charcoal-200); --border-subtle: var(--color-charcoal-300); --muted-background: var(--color-charcoal-100); @@ -516,6 +518,7 @@ --color-inverted-background-hover: var(--inverted-background-hover); --color-warning-background: var(--warning-background); --color-warning-background-hover: var(--warning-background-hover); + --color-success-background: var(--success-background); --color-border-default: var(--border-default); --color-border-subtle: var(--border-subtle); --color-muted-background: var(--muted-background); diff --git a/scripts/cicd/extract-playwright-counts.ts b/scripts/cicd/extract-playwright-counts.ts index ff6f44db3..1a7922816 100755 --- a/scripts/cicd/extract-playwright-counts.ts +++ b/scripts/cicd/extract-playwright-counts.ts @@ -10,37 +10,158 @@ interface TestStats { finished?: number } +interface TestResult { + status: string + duration?: number + error?: { + message?: string + stack?: string + } + attachments?: Array<{ + name: string + path?: string + contentType: string + }> +} + +interface TestCase { + title: string + ok: boolean + outcome: string + results: TestResult[] +} + +interface Suite { + title: string + file: string + suites?: Suite[] + tests?: TestCase[] +} + +interface FullReportData { + stats?: TestStats + suites?: Suite[] +} + interface ReportData { stats?: TestStats } +interface FailedTest { + name: string + file: string + traceUrl?: string + error?: string +} + interface TestCounts { passed: number failed: number flaky: number skipped: number total: number + failures?: FailedTest[] +} + +/** + * Extract failed test details from Playwright report + */ +function extractFailedTests( + reportData: FullReportData, + baseUrl?: string +): FailedTest[] { + const failures: FailedTest[] = [] + + function processTest(test: TestCase, file: string, suitePath: string[]) { + // Check if test failed or is flaky + const hasFailed = test.results.some( + (r) => r.status === 'failed' || r.status === 'timedOut' + ) + const isFlaky = test.outcome === 'flaky' + + if (hasFailed || isFlaky) { + const fullTestName = [...suitePath, test.title] + .filter(Boolean) + .join(' › ') + const failedResult = test.results.find( + (r) => r.status === 'failed' || r.status === 'timedOut' + ) + + // Find trace attachment + let traceUrl: string | undefined + if (failedResult?.attachments) { + const traceAttachment = failedResult.attachments.find( + (a) => a.name === 'trace' && a.contentType === 'application/zip' + ) + if (traceAttachment?.path) { + // Convert local path to URL path + const tracePath = traceAttachment.path.replace(/\\/g, '/') + const traceFile = path.basename(tracePath) + if (baseUrl) { + // Construct trace viewer URL + const traceDataUrl = `${baseUrl}/data/${traceFile}` + traceUrl = `${baseUrl}/trace/?trace=${encodeURIComponent(traceDataUrl)}` + } + } + } + + failures.push({ + name: fullTestName, + file: file, + traceUrl, + error: failedResult?.error?.message + }) + } + } + + function processSuite(suite: Suite, parentPath: string[] = []) { + const suitePath = suite.title ? [...parentPath, suite.title] : parentPath + + // Process tests in this suite + if (suite.tests) { + for (const test of suite.tests) { + processTest(test, suite.file, suitePath) + } + } + + // Recursively process nested suites + if (suite.suites) { + for (const childSuite of suite.suites) { + processSuite(childSuite, suitePath) + } + } + } + + if (reportData.suites) { + for (const suite of reportData.suites) { + processSuite(suite) + } + } + + return failures } /** * Extract test counts from Playwright HTML report * @param reportDir - Path to the playwright-report directory - * @returns Test counts { passed, failed, flaky, skipped, total } + * @param baseUrl - Base URL of the deployed report (for trace links) + * @returns Test counts { passed, failed, flaky, skipped, total, failures } */ -function extractTestCounts(reportDir: string): TestCounts { +function extractTestCounts(reportDir: string, baseUrl?: string): TestCounts { const counts: TestCounts = { passed: 0, failed: 0, flaky: 0, skipped: 0, - total: 0 + total: 0, + failures: [] } try { // First, try to find report.json which Playwright generates with JSON reporter const jsonReportFile = path.join(reportDir, 'report.json') if (fs.existsSync(jsonReportFile)) { - const reportJson: ReportData = JSON.parse( + const reportJson: FullReportData = JSON.parse( fs.readFileSync(jsonReportFile, 'utf-8') ) if (reportJson.stats) { @@ -54,6 +175,12 @@ function extractTestCounts(reportDir: string): TestCounts { counts.failed = stats.unexpected || 0 counts.flaky = stats.flaky || 0 counts.skipped = stats.skipped || 0 + + // Extract detailed failure information + if (counts.failed > 0 || counts.flaky > 0) { + counts.failures = extractFailedTests(reportJson, baseUrl) + } + return counts } } @@ -169,15 +296,18 @@ function extractTestCounts(reportDir: string): TestCounts { // Main execution const reportDir = process.argv[2] +const baseUrl = process.argv[3] // Optional: base URL for trace links if (!reportDir) { - console.error('Usage: extract-playwright-counts.ts ') + console.error( + 'Usage: extract-playwright-counts.ts [base-url]' + ) process.exit(1) } -const counts = extractTestCounts(reportDir) +const counts = extractTestCounts(reportDir, baseUrl) // Output as JSON for easy parsing in shell script -console.log(JSON.stringify(counts)) +process.stdout.write(JSON.stringify(counts) + '\n') -export { extractTestCounts } +export { extractTestCounts, extractFailedTests } diff --git a/scripts/cicd/pr-playwright-deploy-and-comment.sh b/scripts/cicd/pr-playwright-deploy-and-comment.sh index 840203f44..9332e60b7 100755 --- a/scripts/cicd/pr-playwright-deploy-and-comment.sh +++ b/scripts/cicd/pr-playwright-deploy-and-comment.sh @@ -134,23 +134,22 @@ post_comment() { # Main execution if [ "$STATUS" = "starting" ]; then - # Post starting comment + # Post concise starting comment comment=$(cat < **Tests are starting...** +Tests started at $START_TIME UTC -⏰ Started at: $START_TIME UTC +
+📊 Browser Tests -### 🚀 Running Tests -- 🧪 **chromium**: Running tests... -- 🧪 **chromium-0.5x**: Running tests... -- 🧪 **chromium-2x**: Running tests... -- 🧪 **mobile-chrome**: Running tests... +- **chromium**: Running... +- **chromium-0.5x**: Running... +- **chromium-2x**: Running... +- **mobile-chrome**: Running... ---- -⏱️ Please wait while tests are running... +
EOF ) post_comment "$comment" @@ -189,7 +188,8 @@ else if command -v tsx > /dev/null 2>&1 && [ -f "$EXTRACT_SCRIPT" ]; then echo "Extracting counts from $REPORT_DIR using $EXTRACT_SCRIPT" >&2 - counts=$(tsx "$EXTRACT_SCRIPT" "$REPORT_DIR" 2>&1 || echo '{}') + # Pass the base URL so we can generate trace links + counts=$(tsx "$EXTRACT_SCRIPT" "$REPORT_DIR" "$url" 2>&1 || echo '{}') echo "Extracted counts for $browser: $counts" >&2 echo "$counts" > "$temp_dir/$i.counts" else @@ -286,43 +286,74 @@ else # Determine overall status if [ $total_failed -gt 0 ]; then status_icon="❌" - status_text="Some tests failed" + status_text="Failed" elif [ $total_flaky -gt 0 ]; then status_icon="⚠️" - status_text="Tests passed with flaky tests" + status_text="Passed with flaky tests" elif [ $total_tests -gt 0 ]; then status_icon="✅" - status_text="All tests passed!" + status_text="Passed" else status_icon="🕵🏻" - status_text="No test results found" + status_text="No test results" fi - # Generate completion comment + # Generate concise completion comment comment="$COMMENT_MARKER -## 🎭 Playwright Test Results - -$status_icon **$status_text** - -⏰ Completed at: $(date -u '+%m/%d/%Y, %I:%M:%S %p') UTC" +## 🎭 Playwright Tests: $status_icon **$status_text**" # Add summary counts if we have test data if [ $total_tests -gt 0 ]; then comment="$comment -### 📈 Summary -- **Total Tests:** $total_tests -- **Passed:** $total_passed ✅ -- **Failed:** $total_failed $([ $total_failed -gt 0 ] && echo '❌' || echo '') -- **Flaky:** $total_flaky $([ $total_flaky -gt 0 ] && echo '⚠️' || echo '') -- **Skipped:** $total_skipped $([ $total_skipped -gt 0 ] && echo '⏭️' || echo '')" +**Results:** $total_passed passed, $total_failed failed, $total_flaky flaky, $total_skipped skipped (Total: $total_tests)" + fi + + # Extract and display failed tests from all browsers + if [ $total_failed -gt 0 ] || [ $total_flaky -gt 0 ]; then + comment="$comment + +### ❌ Failed Tests" + + # Process each browser's failures + for counts_json in "${counts_array[@]}"; do + [ -z "$counts_json" ] || [ "$counts_json" = "{}" ] && continue + + if command -v jq > /dev/null 2>&1; then + # Extract failures array from JSON + failures=$(echo "$counts_json" | jq -r '.failures // [] | .[]? | "\(.name)|\(.file)|\(.traceUrl // "")"') + + if [ -n "$failures" ]; then + while IFS='|' read -r test_name test_file trace_url; do + [ -z "$test_name" ] && continue + + # Convert file path to GitHub URL (relative to repo root) + github_file_url="https://github.com/$GITHUB_REPOSITORY/blob/$GITHUB_SHA/$test_file" + + # Build the failed test line + test_line="- [$test_name]($github_file_url)" + + if [ -n "$trace_url" ] && [ "$trace_url" != "null" ]; then + test_line="$test_line: [View trace]($trace_url)" + fi + + comment="$comment +$test_line" + done <<< "$failures" + fi + fi + done fi + # Add browser reports in collapsible section comment="$comment -### 📊 Test Reports by Browser" +
+📊 Browser Reports + +" - # Add browser results with individual counts + # Add browser results i=0 IFS=' ' read -r -a browser_array <<< "$BROWSERS" IFS=' ' read -r -a url_array <<< "$urls" @@ -349,7 +380,7 @@ $status_icon **$status_text** fi if [ -n "$b_total" ] && [ "$b_total" != "0" ]; then - counts_str=" • ✅ $b_passed / ❌ $b_failed / ⚠️ $b_flaky / ⏭️ $b_skipped" + counts_str=" (✅ $b_passed / ❌ $b_failed / ⚠️ $b_flaky / ⏭️ $b_skipped)" else counts_str="" fi @@ -358,10 +389,10 @@ $status_icon **$status_text** fi comment="$comment -- ✅ **${browser}**: [View Report](${url})${counts_str}" +- **${browser}**: [View Report](${url})${counts_str}" else comment="$comment -- ❌ **${browser}**: Deployment failed" +- **${browser}**: ❌ Deployment failed" fi i=$((i + 1)) done @@ -369,8 +400,7 @@ $status_icon **$status_text** comment="$comment ---- -🎉 Click on the links above to view detailed test results for each browser configuration." +
" post_comment "$comment" fi diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 627b129e4..301161e5d 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -1 +1,21 @@ -@import '@comfyorg/design-system/css/style.css'; \ No newline at end of file +@import '@comfyorg/design-system/css/style.css'; + +@media (prefers-reduced-motion: no-preference) { + /* List transition animations */ + .list-scale-move, + .list-scale-enter-active, + .list-scale-leave-active { + transition: opacity 150ms ease, transform 150ms ease; + } + + .list-scale-enter-from, + .list-scale-leave-to { + opacity: 0; + transform: scale(70%); + } + + .list-scale-leave-active { + position: absolute; + width: 100%; + } +} diff --git a/src/components/TopMenuSection.vue b/src/components/TopMenuSection.vue index 7cdd5418a..28f2411ea 100644 --- a/src/components/TopMenuSection.vue +++ b/src/components/TopMenuSection.vue @@ -59,8 +59,11 @@ {{ queuedCount }} - - + +