mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 06:35:10 +00:00
Add (premptive) test for reordering linekd widgets
Requires extensive fixture updates
This commit is contained in:
@@ -66,6 +66,12 @@ export class ComfyMouse implements Omit<Mouse, 'move'> {
|
||||
await this.drop(options)
|
||||
}
|
||||
|
||||
async getCenter(locator: Locator): Promise<Position> {
|
||||
const bounds = await locator.boundingBox()
|
||||
if (!bounds) throw new Error('Failed to get bounds' + locator)
|
||||
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 }
|
||||
}
|
||||
|
||||
/** @see {@link Mouse.move} */
|
||||
async move(to: Position, options = ComfyMouse.defaultOptions) {
|
||||
await this.mouse.move(to.x, to.y, options)
|
||||
|
||||
@@ -249,4 +249,33 @@ export class VueNodeHelpers {
|
||||
position: { x: box.width / 2, y: box.height * 0.75 }
|
||||
})
|
||||
}
|
||||
getSlot(name: string | Locator, node?: string | Locator) {
|
||||
const parentLocator = !node
|
||||
? this.page
|
||||
: typeof node === 'string'
|
||||
? this.getNodeByTitle(node)
|
||||
: node
|
||||
const slotLocators = parentLocator
|
||||
.getByTestId('node-widget')
|
||||
.or(parentLocator.locator('.lg-slot'))
|
||||
const filteredLocator =
|
||||
typeof name === 'string'
|
||||
? slotLocators.filter({ hasText: name })
|
||||
: slotLocators.filter({ has: name })
|
||||
return filteredLocator.getByTestId('slot-dot').locator('..')
|
||||
}
|
||||
async isSlotConnected(slot: Locator) {
|
||||
const key = await slot.getByTestId('slot-dot').getAttribute('data-slot-key')
|
||||
if (!key) return false
|
||||
|
||||
return await this.page.evaluate((key) => {
|
||||
const [nodeId, type, slotId] = key.split('-')
|
||||
const node = app?.canvas?.graph?.getNodeById(nodeId)
|
||||
if (!node) return false
|
||||
|
||||
return type === 'in'
|
||||
? node.inputs[Number(slotId)]?.link !== null
|
||||
: !!node.outputs[Number(slotId)].links?.length
|
||||
}, key)
|
||||
}
|
||||
}
|
||||
|
||||
71
browser_tests/fixtures/components/SubgraphEditor.ts
Normal file
71
browser_tests/fixtures/components/SubgraphEditor.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { Locator } from '@playwright/test'
|
||||
|
||||
import { comfyExpect as expect } from '@e2e/fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
|
||||
export class SubgraphEditor {
|
||||
public readonly root
|
||||
|
||||
constructor(protected readonly comfyPage: ComfyPage) {
|
||||
this.root = this.comfyPage.menu.propertiesPanel.root
|
||||
}
|
||||
|
||||
async open(subgraphNode: Locator) {
|
||||
await this.comfyPage.vueNodes.selectNodeByLocator(subgraphNode)
|
||||
// TODO: don't use commands for this
|
||||
await this.comfyPage.command.executeCommand(
|
||||
'Comfy.Graph.EditSubgraphWidgets'
|
||||
)
|
||||
await expect(this.root, 'Open Properties Panel').toBeVisible()
|
||||
}
|
||||
|
||||
resolvePromotionItem(options: {
|
||||
nodeName?: string
|
||||
nodeId?: string
|
||||
widgetName: string
|
||||
}): Locator {
|
||||
const labelLocator = this.comfyPage.page
|
||||
.getByTestId(TestIds.subgraphEditor.widgetLabel)
|
||||
.filter({ hasText: options.widgetName })
|
||||
|
||||
const named = this.root
|
||||
.getByTestId(TestIds.subgraphEditor.widgetItem)
|
||||
.filter({ has: labelLocator })
|
||||
if (!options.nodeName && !options.nodeId) return named
|
||||
if (options.nodeName) {
|
||||
const nodeNameLocator = this.comfyPage.page
|
||||
.getByTestId(TestIds.subgraphEditor.nodeName)
|
||||
.filter({ hasText: options.nodeName })
|
||||
return named.filter({ has: nodeNameLocator })
|
||||
}
|
||||
|
||||
const idLocator = this.comfyPage.page.locator(
|
||||
`[data-nodeid="${options.nodeId}"]`
|
||||
)
|
||||
return named.filter({ has: idLocator })
|
||||
}
|
||||
async togglePromotionOnItem(item: Locator, toState?: boolean) {
|
||||
const toggleButton = item.getByTestId(TestIds.subgraphEditor.iconEye)
|
||||
if (toState !== undefined) {
|
||||
const expectedIcon = `icon-[lucide--eye${toState ? '-off' : ''}]`
|
||||
await expect(toggleButton).toContainClass(expectedIcon)
|
||||
}
|
||||
await toggleButton.click()
|
||||
}
|
||||
|
||||
async togglePromotion(
|
||||
subgraphNode: Locator,
|
||||
options: {
|
||||
nodeName?: string
|
||||
nodeId?: string
|
||||
widgetName: string
|
||||
toState?: boolean
|
||||
}
|
||||
) {
|
||||
await this.open(subgraphNode)
|
||||
|
||||
const item = this.resolvePromotionItem(options)
|
||||
await this.togglePromotionOnItem(item, options.toState)
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,17 @@ import type { ComfyWorkflow } from '@/platform/workflow/management/stores/comfyW
|
||||
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { SubgraphEditor } from '@e2e/fixtures/components/SubgraphEditor'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
import type { NodeReference } from '@e2e/fixtures/utils/litegraphUtils'
|
||||
import { SubgraphSlotReference } from '@e2e/fixtures/utils/litegraphUtils'
|
||||
|
||||
export class SubgraphHelper {
|
||||
constructor(private readonly comfyPage: ComfyPage) {}
|
||||
public readonly editor: SubgraphEditor
|
||||
|
||||
constructor(private readonly comfyPage: ComfyPage) {
|
||||
this.editor = new SubgraphEditor(comfyPage)
|
||||
}
|
||||
|
||||
private get page(): Page {
|
||||
return this.comfyPage.page
|
||||
@@ -327,50 +332,6 @@ export class SubgraphHelper {
|
||||
await this.comfyPage.nextFrame()
|
||||
}
|
||||
|
||||
async toggleContainedWidgetPromotion(
|
||||
subgraphNode: Locator,
|
||||
options: (
|
||||
| { nodeName: string; nodeId?: undefined }
|
||||
| { nodeName?: undefined; nodeId: string }
|
||||
) & { widgetName: string; toState?: boolean }
|
||||
) {
|
||||
await this.comfyPage.vueNodes.selectNodeByLocator(subgraphNode)
|
||||
await this.comfyPage.command.executeCommand(
|
||||
'Comfy.Graph.EditSubgraphWidgets'
|
||||
)
|
||||
const { root } = this.comfyPage.menu.propertiesPanel
|
||||
await expect(root, 'Open Properties Panel').toBeVisible()
|
||||
|
||||
const resolvePromotionItem: () => Locator = () => {
|
||||
const labelLocator = this.comfyPage.page
|
||||
.getByTestId(TestIds.subgraphEditor.widgetLabel)
|
||||
.filter({ hasText: options.widgetName })
|
||||
|
||||
const named = root
|
||||
.getByTestId(TestIds.subgraphEditor.widgetItem)
|
||||
.filter({ has: labelLocator })
|
||||
if (options.nodeName !== undefined) {
|
||||
const nodeNameLocator = this.comfyPage.page
|
||||
.getByTestId(TestIds.subgraphEditor.nodeName)
|
||||
.filter({ hasText: options.nodeName })
|
||||
return named.filter({ has: nodeNameLocator })
|
||||
}
|
||||
|
||||
const idLocator = this.comfyPage.page.locator(
|
||||
`[data-nodeid="${options.nodeId}"]`
|
||||
)
|
||||
return named.filter({ has: idLocator })
|
||||
}
|
||||
const item = resolvePromotionItem()
|
||||
|
||||
const toggleButton = item.getByTestId(TestIds.subgraphEditor.iconEye)
|
||||
if (options.toState !== undefined) {
|
||||
const expectedIcon = `icon-[lucide--eye${options.toState ? '-off' : ''}]`
|
||||
await expect(toggleButton).toContainClass(expectedIcon)
|
||||
}
|
||||
await toggleButton.click()
|
||||
}
|
||||
|
||||
async promoteWidget(nodeLocator: Locator, widgetName: string): Promise<void> {
|
||||
const widget = nodeLocator.getByLabel(widgetName, { exact: true })
|
||||
await this.comfyPage.contextMenu
|
||||
|
||||
@@ -637,17 +637,66 @@ test('@vue-nodes Promote/Demote by side panel', async ({ comfyPage }) => {
|
||||
const subgraphNode = comfyPage.vueNodes.getNodeLocator('2')
|
||||
const steps = comfyPage.vueNodes.getWidgetByName('New Subgraph', 'steps')
|
||||
|
||||
await comfyPage.subgraph.toggleContainedWidgetPromotion(subgraphNode, {
|
||||
await comfyPage.subgraph.editor.togglePromotion(subgraphNode, {
|
||||
nodeName: 'KSampler',
|
||||
widgetName: 'steps',
|
||||
toState: true
|
||||
})
|
||||
await expect(steps, 'Promote widget').toBeVisible()
|
||||
|
||||
await comfyPage.subgraph.toggleContainedWidgetPromotion(subgraphNode, {
|
||||
await comfyPage.subgraph.editor.togglePromotion(subgraphNode, {
|
||||
nodeName: 'KSampler',
|
||||
widgetName: 'steps',
|
||||
toState: false
|
||||
})
|
||||
await expect(steps, 'Un-promote widget').toBeHidden()
|
||||
})
|
||||
|
||||
test('@vue-nodes Can intermix linked and proxy', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('subgraphs/basic-subgraph')
|
||||
const subgraphNode = comfyPage.vueNodes.getNodeLocator('2')
|
||||
|
||||
await test.step('Enter subgraph and link widget to input', async () => {
|
||||
await comfyPage.vueNodes.enterSubgraph('2')
|
||||
|
||||
const ksampler = comfyPage.vueNodes.getNodeByTitle('KSampler')
|
||||
await comfyPage.subgraph.promoteWidget(ksampler, 'cfg')
|
||||
|
||||
const fromSlot = await comfyPage.vueNodes.getSlot('steps', ksampler)
|
||||
const toPos = await comfyPage.subgraph.getInputSlot().getOpenSlotPosition()
|
||||
await fromSlot.dragTo(comfyPage.canvas, { targetPosition: toPos })
|
||||
|
||||
const isConnected = () => comfyPage.vueNodes.isSlotConnected(fromSlot)
|
||||
await expect.poll(isConnected).toBe(true)
|
||||
|
||||
await comfyPage.subgraph.exitViaBreadcrumb()
|
||||
})
|
||||
|
||||
await expect(
|
||||
subgraphNode.locator('.lg-node-widget').first(),
|
||||
'linked widgets are first by default'
|
||||
).toHaveText('steps')
|
||||
|
||||
const { editor } = comfyPage.subgraph
|
||||
await editor.open(subgraphNode)
|
||||
const stepsItem = editor.resolvePromotionItem({ widgetName: 'steps' })
|
||||
const cfgItem = editor.resolvePromotionItem({ widgetName: 'cfg' })
|
||||
|
||||
await comfyMouse.move(await comfyMouse.getCenter(stepsItem))
|
||||
await comfyMouse.down()
|
||||
const { x, y, width, height } = (await cfgItem.boundingBox())!
|
||||
await comfyMouse.move({ x: x + width / 2, y: y + height })
|
||||
await comfyMouse.up()
|
||||
|
||||
const firstItem = editor.resolvePromotionItem({ widgetName: '' }).first()
|
||||
await expect(firstItem, 'Swap widget order').toContainText('cfg')
|
||||
|
||||
// TODO: fix actual bug.
|
||||
await expect(
|
||||
subgraphNode.locator('.lg-node-widget').first(),
|
||||
'Linked widget is first on node'
|
||||
).not.toHaveText('cfg')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user