mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 22:39:39 +00:00
## Summary Add integration contract tests (unit) and expanded Playwright coverage for subgraph promotion, hydration, navigation, and lifecycle edge behaviors. ## Changes - **What**: 22 unit/integration tests across 9 files covering promotion store sync, widget view lifecycle, input link resolution, pseudo-widget cache, navigation viewport restore, and subgraph operations. 13 Playwright E2E tests covering proxyWidgets hydration stability, promoted source removal cleanup, pseudo-preview unpack/remove, multi-link representative round-trip, nested promotion retarget, and navigation state on workflow switch. - **Helpers**: Added `isPseudoPreviewEntry`, `getPseudoPreviewWidgets`, `getNonPreviewPromotedWidgets` to promotedWidgets helper. Added `SubgraphHelper.getNodeCount()`. ## Review Focus - Test-only PR — no production code changes - Validates existing subgraph behaviors are covered by regression tests before further feature work - Phase 4 (unit/integration contracts) and Phase 5 (Playwright expansion) of the subgraph test coverage plan ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10123-test-subgraph-integration-contracts-and-expanded-Playwright-coverage-3256d73d365081258023e3a763859e00) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com>
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
|
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
|
|
|
test.describe('Subgraph duplicate ID remapping', { tag: ['@subgraph'] }, () => {
|
|
const WORKFLOW = 'subgraphs/subgraph-nested-duplicate-ids'
|
|
|
|
test('All node IDs are globally unique after loading', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.loadWorkflow(WORKFLOW)
|
|
|
|
const result = await comfyPage.page.evaluate(() => {
|
|
const graph = window.app!.canvas.graph!
|
|
// TODO: Extract allGraphs accessor (root + subgraphs) into LGraph
|
|
// TODO: Extract allNodeIds accessor into LGraph
|
|
const allGraphs = [graph, ...graph.subgraphs.values()]
|
|
const allIds = allGraphs
|
|
.flatMap((g) => g._nodes)
|
|
.map((n) => n.id)
|
|
.filter((id): id is number => typeof id === 'number')
|
|
|
|
return { allIds, uniqueCount: new Set(allIds).size }
|
|
})
|
|
|
|
expect(result.uniqueCount).toBe(result.allIds.length)
|
|
expect(result.allIds.length).toBeGreaterThanOrEqual(10)
|
|
})
|
|
|
|
test('Root graph node IDs are preserved as canonical', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.loadWorkflow(WORKFLOW)
|
|
|
|
const rootIds = await comfyPage.page.evaluate(() => {
|
|
const graph = window.app!.canvas.graph!
|
|
return graph._nodes
|
|
.map((n) => n.id)
|
|
.filter((id): id is number => typeof id === 'number')
|
|
.sort((a, b) => a - b)
|
|
})
|
|
|
|
expect(rootIds).toEqual([1, 2, 5])
|
|
})
|
|
|
|
test('Promoted widget tuples are stable after full page reload boot path', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.loadWorkflow(WORKFLOW)
|
|
await comfyPage.nextFrame()
|
|
|
|
const beforeSnapshot =
|
|
await comfyPage.subgraph.getHostPromotedTupleSnapshot()
|
|
expect(beforeSnapshot.length).toBeGreaterThan(0)
|
|
expect(
|
|
beforeSnapshot.some(({ promotedWidgets }) => promotedWidgets.length > 0)
|
|
).toBe(true)
|
|
|
|
await comfyPage.page.reload()
|
|
await comfyPage.page.waitForFunction(() => !!window.app)
|
|
await comfyPage.workflow.loadWorkflow(WORKFLOW)
|
|
await comfyPage.nextFrame()
|
|
|
|
await expect(async () => {
|
|
const afterSnapshot =
|
|
await comfyPage.subgraph.getHostPromotedTupleSnapshot()
|
|
expect(afterSnapshot).toEqual(beforeSnapshot)
|
|
}).toPass({ timeout: 5_000 })
|
|
})
|
|
|
|
test('All links reference valid nodes in their graph', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.loadWorkflow(WORKFLOW)
|
|
|
|
const invalidLinks = await comfyPage.page.evaluate(() => {
|
|
const graph = window.app!.canvas.graph!
|
|
const labeledGraphs: [string, typeof graph][] = [
|
|
['root', graph],
|
|
...[...graph.subgraphs.entries()].map(
|
|
([id, sg]) => [`subgraph:${id}`, sg] as [string, typeof graph]
|
|
)
|
|
]
|
|
|
|
const isNonNegative = (id: number | string) =>
|
|
typeof id === 'number' && id >= 0
|
|
|
|
return labeledGraphs.flatMap(([label, g]) =>
|
|
[...g._links.values()].flatMap((link) =>
|
|
[
|
|
isNonNegative(link.origin_id) &&
|
|
!g._nodes_by_id[link.origin_id] &&
|
|
`${label}: origin_id ${link.origin_id} not found`,
|
|
isNonNegative(link.target_id) &&
|
|
!g._nodes_by_id[link.target_id] &&
|
|
`${label}: target_id ${link.target_id} not found`
|
|
].filter(Boolean)
|
|
)
|
|
)
|
|
})
|
|
|
|
expect(invalidLinks).toEqual([])
|
|
})
|
|
|
|
test('Subgraph navigation works after ID remapping', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.loadWorkflow(WORKFLOW)
|
|
|
|
const subgraphNode = await comfyPage.nodeOps.getNodeRefById('5')
|
|
await subgraphNode.navigateIntoSubgraph()
|
|
|
|
const isInSubgraph = () =>
|
|
comfyPage.page.evaluate(
|
|
() => window.app!.canvas.graph?.isRootGraph === false
|
|
)
|
|
|
|
expect(await isInSubgraph()).toBe(true)
|
|
|
|
await comfyPage.page.keyboard.press('Escape')
|
|
await comfyPage.nextFrame()
|
|
|
|
expect(await isInSubgraph()).toBe(false)
|
|
})
|
|
})
|