Files
ComfyUI_frontend/browser_tests/tests/subgraph-duplicate-ids.spec.ts
Alexander Brown d2358c83e8 test: extract shared subgraph E2E test utilities (#10629)
## Summary

Extract repeated patterns from 12 subgraph Playwright spec files into
shared test utilities, reducing duplication by ~142 lines.

## Changes

- **What**: New shared helpers for common subgraph test operations:
- `SubgraphHelper`: `getSlotCount()`, `getSlotLabel()`, `removeSlot()`,
`findSubgraphNodeId()`
  - `NodeReference`: `delete()`
- `subgraphTestUtils`: `serializeAndReload()`,
`convertDefaultKSamplerToSubgraph()`, `expectWidgetBelowHeader()`,
`collectConsoleWarnings()`, `packAllInteriorNodes()`
- Replaced ~72 inline `page.evaluate` blocks and multi-line sequences
with single helper calls across 12 spec files

## Review Focus

- Behavioral equivalence: every replacement is a mechanical extraction
with no test logic changes
- API surface of new helpers: naming, parameter types, placement in
existing utility classes
- Whether any remaining inline patterns in the spec files would benefit
from further extraction

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10629-test-extract-shared-subgraph-E2E-test-utilities-3306d73d365081b0b6b5db52ed0a4552)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-03-28 21:12:26 +00:00

121 lines
3.8 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()
expect(await comfyPage.subgraph.isInSubgraph()).toBe(true)
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
expect(await comfyPage.subgraph.isInSubgraph()).toBe(false)
})
})