mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
Backport of #9121 to `core/1.42` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10865-backport-core-1-42-fix-resolve-subgraph-node-slot-link-misalignment-during-workflow-l-33a6d73d365081b088d3feb3c73e0a5c) by [Unito](https://www.unito.io) Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <448862+DrJKL@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
133 lines
4.2 KiB
TypeScript
133 lines
4.2 KiB
TypeScript
import { readFileSync } from 'fs'
|
|
import { resolve } from 'path'
|
|
|
|
import { expect } from '@playwright/test'
|
|
|
|
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
|
|
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
|
|
|
interface SlotMeasurement {
|
|
key: string
|
|
offsetX: number
|
|
offsetY: number
|
|
}
|
|
|
|
interface NodeSlotData {
|
|
nodeId: string
|
|
isSubgraph: boolean
|
|
nodeW: number
|
|
nodeH: number
|
|
slots: SlotMeasurement[]
|
|
}
|
|
|
|
/**
|
|
* Regression test for link misalignment on SubgraphNodes when loading
|
|
* workflows with workflowRendererVersion: "LG".
|
|
*
|
|
* Root cause: ensureCorrectLayoutScale scales nodes by 1.2x for LG workflows,
|
|
* and fitView() updates lgCanvas.ds immediately. The Vue TransformPane's CSS
|
|
* transform lags by a frame, causing clientPosToCanvasPos to produce wrong
|
|
* slot offsets. The fix uses DOM-relative measurement instead.
|
|
*/
|
|
test.describe(
|
|
'Subgraph slot alignment after LG layout scale',
|
|
{ tag: ['@subgraph', '@canvas'] },
|
|
() => {
|
|
test('slot positions stay within node bounds after loading LG workflow', async ({
|
|
comfyPage
|
|
}) => {
|
|
const SLOT_BOUNDS_MARGIN = 20
|
|
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
|
|
|
const workflowPath = resolve(
|
|
import.meta.dirname,
|
|
'../assets/subgraphs/basic-subgraph.json'
|
|
)
|
|
const workflow = JSON.parse(
|
|
readFileSync(workflowPath, 'utf-8')
|
|
) as ComfyWorkflowJSON
|
|
workflow.extra = {
|
|
...workflow.extra,
|
|
workflowRendererVersion: 'LG'
|
|
}
|
|
|
|
await comfyPage.page.evaluate(
|
|
(wf) =>
|
|
window.app!.loadGraphData(wf as ComfyWorkflowJSON, true, true, null, {
|
|
openSource: 'template'
|
|
}),
|
|
workflow
|
|
)
|
|
await comfyPage.nextFrame()
|
|
|
|
// Wait for slot elements to appear in DOM
|
|
await comfyPage.page.locator('[data-slot-key]').first().waitFor()
|
|
|
|
const result: NodeSlotData[] = await comfyPage.page.evaluate(() => {
|
|
const nodes = window.app!.graph._nodes
|
|
const slotData: NodeSlotData[] = []
|
|
|
|
for (const node of nodes) {
|
|
const nodeId = String(node.id)
|
|
const nodeEl = document.querySelector(
|
|
`[data-node-id="${nodeId}"]`
|
|
) as HTMLElement | null
|
|
if (!nodeEl) continue
|
|
|
|
const slotEls = nodeEl.querySelectorAll('[data-slot-key]')
|
|
if (slotEls.length === 0) continue
|
|
|
|
const slots: SlotMeasurement[] = []
|
|
|
|
const nodeRect = nodeEl.getBoundingClientRect()
|
|
for (const slotEl of slotEls) {
|
|
const slotRect = slotEl.getBoundingClientRect()
|
|
const slotKey = (slotEl as HTMLElement).dataset.slotKey ?? 'unknown'
|
|
slots.push({
|
|
key: slotKey,
|
|
offsetX: slotRect.left + slotRect.width / 2 - nodeRect.left,
|
|
offsetY: slotRect.top + slotRect.height / 2 - nodeRect.top
|
|
})
|
|
}
|
|
|
|
slotData.push({
|
|
nodeId,
|
|
isSubgraph: !!node.isSubgraphNode?.(),
|
|
nodeW: nodeRect.width,
|
|
nodeH: nodeRect.height,
|
|
slots
|
|
})
|
|
}
|
|
|
|
return slotData
|
|
})
|
|
|
|
const subgraphNodes = result.filter((n) => n.isSubgraph)
|
|
expect(subgraphNodes.length).toBeGreaterThan(0)
|
|
|
|
for (const node of subgraphNodes) {
|
|
for (const slot of node.slots) {
|
|
expect(
|
|
slot.offsetX,
|
|
`Slot ${slot.key} on node ${node.nodeId}: X offset ${slot.offsetX} outside node width ${node.nodeW}`
|
|
).toBeGreaterThanOrEqual(-SLOT_BOUNDS_MARGIN)
|
|
expect(
|
|
slot.offsetX,
|
|
`Slot ${slot.key} on node ${node.nodeId}: X offset ${slot.offsetX} outside node width ${node.nodeW}`
|
|
).toBeLessThanOrEqual(node.nodeW + SLOT_BOUNDS_MARGIN)
|
|
|
|
expect(
|
|
slot.offsetY,
|
|
`Slot ${slot.key} on node ${node.nodeId}: Y offset ${slot.offsetY} outside node height ${node.nodeH}`
|
|
).toBeGreaterThanOrEqual(-SLOT_BOUNDS_MARGIN)
|
|
expect(
|
|
slot.offsetY,
|
|
`Slot ${slot.key} on node ${node.nodeId}: Y offset ${slot.offsetY} outside node height ${node.nodeH}`
|
|
).toBeLessThanOrEqual(node.nodeH + SLOT_BOUNDS_MARGIN)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
)
|