From 369da53743a983a129a70817a311b79adcd4c080 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Fri, 19 Sep 2025 13:02:44 -0700 Subject: [PATCH] review comments --- browser_tests/fixtures/ComfyPage.ts | 95 ---------------- browser_tests/helpers/fitToView.ts | 104 ++++++++++++++++++ .../tests/vueNodes/linkInteraction.spec.ts | 48 ++------ 3 files changed, 112 insertions(+), 135 deletions(-) create mode 100644 browser_tests/helpers/fitToView.ts diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index cad95a7aa..c9a8820f5 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -29,8 +29,6 @@ dotenv.config() type WorkspaceStore = ReturnType -type Bounds = readonly [number, number, number, number] - class ComfyMenu { private _nodeLibraryTab: NodeLibrarySidebarTab | null = null private _workflowsTab: WorkflowsSidebarTab | null = null @@ -340,99 +338,6 @@ export class ComfyPage { return `./browser_tests/assets/${fileName}` } - async fitToView( - options: { - selectionOnly?: boolean - zoom?: number - padding?: number - } = {} - ) { - const { selectionOnly = false, zoom = 0.75, padding = 10 } = options - - const rectangles = await this.page.evaluate< - Bounds[] | null, - { selectionOnly: boolean } - >( - ({ selectionOnly }) => { - const app = window['app'] - if (!app?.canvas) return null - - const canvas = app.canvas - const items = (() => { - if (selectionOnly && canvas.selectedItems?.size) { - return Array.from(canvas.selectedItems) - } - try { - return Array.from(canvas.positionableItems ?? []) - } catch { - return [] - } - })() - - if (!items.length) return null - - const rects: Bounds[] = [] - for (const item of items) { - const rect = item?.boundingRect - if (!rect) continue - - const x = Number(rect[0]) - const y = Number(rect[1]) - const width = Number(rect[2]) - const height = Number(rect[3]) - - rects.push([x, y, width, height] as Bounds) - } - - return rects.length ? rects : null - }, - { selectionOnly } - ) - - if (!rectangles || rectangles.length === 0) return - - let minX = Infinity - let minY = Infinity - let maxX = -Infinity - let maxY = -Infinity - - for (const [x, y, width, height] of rectangles) { - minX = Math.min(minX, x) - minY = Math.min(minY, y) - maxX = Math.max(maxX, x + width) - maxY = Math.max(maxY, y + height) - } - - const hasFiniteBounds = - Number.isFinite(minX) && - Number.isFinite(minY) && - Number.isFinite(maxX) && - Number.isFinite(maxY) - - if (!hasFiniteBounds) return - - const boundsArray = [ - minX - padding, - minY - padding, - maxX - minX + 2 * padding, - maxY - minY + 2 * padding - ] as Bounds - - await this.page.evaluate( - ({ bounds, zoom }) => { - const app = window['app'] - if (!app?.canvas) return - - const canvas = app.canvas - canvas.ds.fitToBounds(bounds, { zoom }) - canvas.setDirty(true, true) - }, - { bounds: boundsArray, zoom } - ) - - await this.nextFrame() - } - async executeCommand(commandId: string) { await this.page.evaluate((id: string) => { return window['app'].extensionManager.command.execute(id) diff --git a/browser_tests/helpers/fitToView.ts b/browser_tests/helpers/fitToView.ts new file mode 100644 index 000000000..af6c10e9d --- /dev/null +++ b/browser_tests/helpers/fitToView.ts @@ -0,0 +1,104 @@ +import type { ReadOnlyRect } from '../../src/lib/litegraph/src/interfaces' +import type { ComfyPage } from '../fixtures/ComfyPage' + +interface FitToViewOptions { + selectionOnly?: boolean + zoom?: number + padding?: number +} + +/** + * Instantly fits the canvas view to graph content without waiting for UI animation. + * + * Lives outside the shared fixture to keep the default ComfyPage interactions user-oriented. + */ +export async function fitToViewInstant( + comfyPage: ComfyPage, + options: FitToViewOptions = {} +) { + const { selectionOnly = false, zoom = 0.75, padding = 10 } = options + + const rectangles = await comfyPage.page.evaluate< + ReadOnlyRect[] | null, + { selectionOnly: boolean } + >( + ({ selectionOnly }) => { + const app = window['app'] + if (!app?.canvas) return null + + const canvas = app.canvas + const items = (() => { + if (selectionOnly && canvas.selectedItems?.size) { + return Array.from(canvas.selectedItems) + } + try { + return Array.from(canvas.positionableItems ?? []) + } catch { + return [] + } + })() + + if (!items.length) return null + + const rects: ReadOnlyRect[] = [] + + for (const item of items) { + const rect = item?.boundingRect + if (!rect) continue + + const x = Number(rect[0]) + const y = Number(rect[1]) + const width = Number(rect[2]) + const height = Number(rect[3]) + + rects.push([x, y, width, height] as const) + } + + return rects.length ? rects : null + }, + { selectionOnly } + ) + + if (!rectangles || rectangles.length === 0) return + + let minX = Infinity + let minY = Infinity + let maxX = -Infinity + let maxY = -Infinity + + for (const [x, y, width, height] of rectangles) { + minX = Math.min(minX, Number(x)) + minY = Math.min(minY, Number(y)) + maxX = Math.max(maxX, Number(x) + Number(width)) + maxY = Math.max(maxY, Number(y) + Number(height)) + } + + const hasFiniteBounds = + Number.isFinite(minX) && + Number.isFinite(minY) && + Number.isFinite(maxX) && + Number.isFinite(maxY) + + if (!hasFiniteBounds) return + + const bounds: ReadOnlyRect = [ + minX - padding, + minY - padding, + maxX - minX + 2 * padding, + maxY - minY + 2 * padding + ] + + await comfyPage.page.evaluate( + ({ bounds, zoom }) => { + const app = window['app'] + if (!app?.canvas) return + + const canvas = app.canvas + canvas.ds.fitToBounds(bounds, { zoom }) + canvas.setDirty(true, true) + }, + { bounds, zoom } + ) + + await comfyPage.nextFrame() +} diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts b/browser_tests/tests/vueNodes/linkInteraction.spec.ts index 80e758414..d6b1bccc1 100644 --- a/browser_tests/tests/vueNodes/linkInteraction.spec.ts +++ b/browser_tests/tests/vueNodes/linkInteraction.spec.ts @@ -5,6 +5,7 @@ import { comfyExpect as expect, comfyPageFixture as test } from '../../fixtures/ComfyPage' +import { fitToViewInstant } from '../../helpers/fitToView' async function getCenter(locator: Locator): Promise<{ x: number; y: number }> { const box = await locator.boundingBox() @@ -22,7 +23,7 @@ test.describe('Vue Node Link Interaction', () => { await comfyPage.setup() await comfyPage.loadWorkflow('vueNodes/simple-triple') await comfyPage.vueNodes.waitForNodes() - await comfyPage.fitToView() + await fitToViewInstant(comfyPage) }) test('should show a link dragging out from a slot when dragging on a slot', async ({ @@ -65,8 +66,7 @@ test.describe('Vue Node Link Interaction', () => { }) test('should create a link when dropping on a compatible slot', async ({ - comfyPage, - comfyMouse + comfyPage }) => { const samplerNodes = await comfyPage.getNodeRefsByType('KSampler') expect(samplerNodes.length).toBeGreaterThan(0) @@ -92,17 +92,7 @@ test.describe('Vue Node Link Interaction', () => { await expect(outputSlot).toBeVisible() await expect(inputSlot).toBeVisible() - const start = await getCenter(outputSlot) - const target = await getCenter(inputSlot) - - await comfyMouse.move(start) - - try { - await comfyMouse.drag(target) - } finally { - await comfyMouse.drop() - } - + await outputSlot.dragTo(inputSlot) await comfyPage.nextFrame() expect(await samplerOutput.getLinkCount()).toBe(1) @@ -140,8 +130,7 @@ test.describe('Vue Node Link Interaction', () => { }) test('should not create a link when slot types are incompatible', async ({ - comfyPage, - comfyMouse + comfyPage }) => { const samplerNodes = await comfyPage.getNodeRefsByType('KSampler') expect(samplerNodes.length).toBeGreaterThan(0) @@ -167,17 +156,7 @@ test.describe('Vue Node Link Interaction', () => { await expect(outputSlot).toBeVisible() await expect(inputSlot).toBeVisible() - const start = await getCenter(outputSlot) - const target = await getCenter(inputSlot) - - await comfyMouse.move(start) - - try { - await comfyMouse.drag(target) - } finally { - await comfyMouse.drop() - } - + await outputSlot.dragTo(inputSlot) await comfyPage.nextFrame() expect(await samplerOutput.getLinkCount()).toBe(0) @@ -198,8 +177,7 @@ test.describe('Vue Node Link Interaction', () => { }) test('should not create a link when dropping onto a slot on the same node', async ({ - comfyPage, - comfyMouse + comfyPage }) => { const samplerNodes = await comfyPage.getNodeRefsByType('KSampler') expect(samplerNodes.length).toBeGreaterThan(0) @@ -221,17 +199,7 @@ test.describe('Vue Node Link Interaction', () => { await expect(outputSlot).toBeVisible() await expect(inputSlot).toBeVisible() - const start = await getCenter(outputSlot) - const target = await getCenter(inputSlot) - - await comfyMouse.move(start) - - try { - await comfyMouse.drag(target) - } finally { - await comfyMouse.drop() - } - + await outputSlot.dragTo(inputSlot) await comfyPage.nextFrame() expect(await samplerOutput.getLinkCount()).toBe(0)