Files
ComfyUI_frontend/browser_tests/tests/subgraph-promoted-slot-position.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

96 lines
3.3 KiB
TypeScript

import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
import { SubgraphHelper } from '../fixtures/helpers/SubgraphHelper'
test.describe(
'Subgraph promoted widget-input slot position',
{ tag: '@subgraph' },
() => {
test('Promoted text widget slot is positioned at widget row, not header', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow(
'subgraphs/subgraph-with-promoted-text-widget'
)
// Render a few frames so arrange() runs
await comfyPage.nextFrame()
await comfyPage.nextFrame()
const result = await SubgraphHelper.getTextSlotPosition(
comfyPage.page,
'11'
)
expect(result).not.toBeNull()
expect(result!.hasPos).toBe(true)
// The slot Y position should be well below the title area.
// If it's near 0 or negative, the slot is stuck at the header (the bug).
expect(result!.posY).toBeGreaterThan(result!.titleHeight)
})
test('Slot position remains correct after renaming subgraph input label', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow(
'subgraphs/subgraph-with-promoted-text-widget'
)
await comfyPage.nextFrame()
await comfyPage.nextFrame()
// Verify initial position is correct
const before = await SubgraphHelper.getTextSlotPosition(
comfyPage.page,
'11'
)
expect(before).not.toBeNull()
expect(before!.hasPos).toBe(true)
expect(before!.posY).toBeGreaterThan(before!.titleHeight)
// Navigate into subgraph and rename the text input
const subgraphNode = await comfyPage.nodeOps.getNodeRefById('11')
await subgraphNode.navigateIntoSubgraph()
const initialLabel = await comfyPage.page.evaluate(() => {
const graph = window.app!.canvas.graph
if (!graph || !('inputNode' in graph)) return null
const textInput = graph.inputs?.find(
(i: { type: string }) => i.type === 'STRING'
)
return textInput?.label || textInput?.name || null
})
if (!initialLabel)
throw new Error('Could not find STRING input in subgraph')
await comfyPage.subgraph.rightClickInputSlot(initialLabel)
await comfyPage.contextMenu.clickLitegraphMenuItem('Rename Slot')
await comfyPage.nextFrame()
const dialog = '.graphdialog input'
await comfyPage.page.waitForSelector(dialog, { state: 'visible' })
await comfyPage.page.fill(dialog, '')
await comfyPage.page.fill(dialog, 'my_custom_prompt')
await comfyPage.page.keyboard.press('Enter')
await comfyPage.page.waitForSelector(dialog, { state: 'hidden' })
// Navigate back to parent graph
await comfyPage.subgraph.exitViaBreadcrumb()
// Verify slot position is still at the widget row after rename
const after = await SubgraphHelper.getTextSlotPosition(
comfyPage.page,
'11'
)
expect(after).not.toBeNull()
expect(after!.hasPos).toBe(true)
expect(after!.posY).toBeGreaterThan(after!.titleHeight)
// widget.name is the stable identity key — it does NOT change on rename.
// The display label is on input.label, read via PromotedWidgetView.label.
expect(after!.widgetName).not.toBe('my_custom_prompt')
})
}
)