diff --git a/browser_tests/fixtures/helpers/NodeOperationsHelper.ts b/browser_tests/fixtures/helpers/NodeOperationsHelper.ts index a0fadd2f74..5f21069516 100644 --- a/browser_tests/fixtures/helpers/NodeOperationsHelper.ts +++ b/browser_tests/fixtures/helpers/NodeOperationsHelper.ts @@ -216,16 +216,6 @@ export class NodeOperationsHelper { } } - async convertAllNodesToGroupNode(groupNodeName: string): Promise { - await this.comfyPage.canvas.press('Control+a') - const node = await this.getFirstNodeRef() - if (!node) { - throw new Error('No nodes found to convert') - } - await node.clickContextMenuOption('Convert to Group Node') - await this.fillPromptDialog(groupNodeName) - } - async fillPromptDialog(value: string): Promise { await this.promptDialogInput.fill(value) await this.page.keyboard.press('Enter') diff --git a/browser_tests/fixtures/utils/litegraphUtils.ts b/browser_tests/fixtures/utils/litegraphUtils.ts index 0036526a65..1a5d02de9d 100644 --- a/browser_tests/fixtures/utils/litegraphUtils.ts +++ b/browser_tests/fixtures/utils/litegraphUtils.ts @@ -514,17 +514,6 @@ export class NodeReference { const ctx = this.comfyPage.page.locator('.litecontextmenu') await ctx.getByText(optionText).click() } - async convertToGroupNode(groupNodeName: string = 'GroupNode') { - await this.clickContextMenuOption('Convert to Group Node') - await this.comfyPage.nodeOps.fillPromptDialog(groupNodeName) - const nodes = await this.comfyPage.nodeOps.getNodeRefsByType( - `workflow>${groupNodeName}` - ) - if (nodes.length !== 1) { - throw new Error(`Did not find single group node (found=${nodes.length})`) - } - return nodes[0] - } async convertToSubgraph() { await this.clickContextMenuOption('Convert to Subgraph') await this.comfyPage.nextFrame() diff --git a/browser_tests/tests/groupNode.spec.ts b/browser_tests/tests/groupNode.spec.ts index d002d83681..0a5c3edcb3 100644 --- a/browser_tests/tests/groupNode.spec.ts +++ b/browser_tests/tests/groupNode.spec.ts @@ -5,11 +5,14 @@ import { comfyExpect as expect, comfyPageFixture as test } from '@e2e/fixtures/ComfyPage' -import type { NodeLibrarySidebarTab } from '@e2e/fixtures/components/SidebarTab' import { TestIds } from '@e2e/fixtures/selectors' -import { DefaultGraphPositions } from '@e2e/fixtures/constants/defaultGraphPositions' import type { NodeReference } from '@e2e/fixtures/utils/litegraphUtils' +// Group Node creation has been removed from the UI. These tests cover the +// surviving behaviors: loading legacy workflows that contain group nodes, +// copy/paste of already-loaded group nodes, and opening the Manage Group +// Node dialog from an existing group node. + test.beforeEach(async ({ comfyPage }) => { await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled') await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false) @@ -17,151 +20,14 @@ test.beforeEach(async ({ comfyPage }) => { }) test.describe('Group Node', { tag: '@node' }, () => { - test.describe('Node library sidebar', () => { - const groupNodeName = 'DefautWorkflowGroupNode' - const groupNodeCategory = 'group nodes>workflow' - const groupNodeBookmarkName = `workflow>${groupNodeName}` - let libraryTab: NodeLibrarySidebarTab - - test.beforeEach(async ({ comfyPage }) => { - await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top') - libraryTab = comfyPage.menu.nodeLibraryTab - await comfyPage.nodeOps.convertAllNodesToGroupNode(groupNodeName) - await libraryTab.open() - }) - - test('Is added to node library sidebar', async ({ - comfyPage: _comfyPage - }) => { - await expect(libraryTab.getFolder(groupNodeCategory)).toHaveCount(1) - }) - - test('Can be added to canvas using node library sidebar', async ({ - comfyPage - }) => { - const initialNodeCount = await comfyPage.nodeOps.getGraphNodesCount() - - // Add group node from node library sidebar - await libraryTab.getFolder(groupNodeCategory).click() - await libraryTab.getNode(groupNodeName).click() - - // Verify the node is added to the canvas - await expect - .poll(() => comfyPage.nodeOps.getGraphNodesCount()) - .toBe(initialNodeCount + 1) - }) - - test('Can be bookmarked and unbookmarked', async ({ comfyPage }) => { - await libraryTab.getFolder(groupNodeCategory).click() - await libraryTab - .getNode(groupNodeName) - .locator('.bookmark-button') - .click() - - // Verify the node is added to the bookmarks tab - await expect - .poll(() => - comfyPage.settings.getSetting('Comfy.NodeLibrary.Bookmarks.V2') - ) - .toEqual([groupNodeBookmarkName]) - // Verify the bookmark node with the same name is added to the tree - await expect(libraryTab.getNode(groupNodeName)).not.toHaveCount(0) - - // Unbookmark the node - await libraryTab - .getNode(groupNodeName) - .locator('.bookmark-button') - .first() - .click() - - // Verify the node is removed from the bookmarks tab - await expect - .poll(() => - comfyPage.settings.getSetting('Comfy.NodeLibrary.Bookmarks.V2') - ) - .toHaveLength(0) - }) - - test('Displays preview on bookmark hover', async ({ comfyPage }) => { - await libraryTab.getFolder(groupNodeCategory).click() - await libraryTab - .getNode(groupNodeName) - .locator('.bookmark-button') - .click() - await comfyPage.page - .locator('.p-tree-node-label.tree-explorer-node-label') - .first() - .hover() - await expect( - comfyPage.page.locator('.node-lib-node-preview') - ).toBeVisible() - await libraryTab - .getNode(groupNodeName) - .locator('.bookmark-button') - .first() - .click() - }) - }) - - test( - 'Can be added to canvas using search', - { tag: '@screenshot' }, - async ({ comfyPage }) => { - const groupNodeName = 'DefautWorkflowGroupNode' - await comfyPage.nodeOps.convertAllNodesToGroupNode(groupNodeName) - await comfyPage.canvasOps.doubleClick() - await comfyPage.nextFrame() - await comfyPage.searchBox.input.waitFor({ state: 'visible' }) - await comfyPage.searchBox.input.fill(groupNodeName) - await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' }) - - const exactGroupNodeResult = comfyPage.searchBox.dropdown - .locator(`li[aria-label="${groupNodeName}"]`) - .first() - await expect(exactGroupNodeResult).toBeVisible() - await exactGroupNodeResult.click() - - await expect(comfyPage.canvas).toHaveScreenshot( - 'group-node-copy-added-from-search.png' - ) - } - ) - - test('Displays tooltip on title hover', async ({ comfyPage }) => { - await comfyPage.settings.setSetting('Comfy.EnableTooltips', true) - await comfyPage.nodeOps.convertAllNodesToGroupNode('Group Node') - await comfyPage.page.mouse.move(47, 173) - await expect(comfyPage.page.locator('.node-tooltip')).toBeVisible() - }) - - test('Manage group opens with the correct group selected', async ({ + test('Loads from a workflow using the legacy path separator ("/")', async ({ comfyPage }) => { - const makeGroup = async (name: string, type1: string, type2: string) => { - const node1 = (await comfyPage.nodeOps.getNodeRefsByType(type1))[0] - const node2 = (await comfyPage.nodeOps.getNodeRefsByType(type2))[0] - await node1.click('title') - await node2.click('title', { - modifiers: ['Shift'] - }) - return await node2.convertToGroupNode(name) - } - - const group1 = await makeGroup( - 'g1', - 'CLIPTextEncode', - 'CheckpointLoaderSimple' - ) - const group2 = await makeGroup('g2', 'EmptyLatentImage', 'KSampler') - - const manage1 = await group1.manageGroupNode() - await comfyPage.nextFrame() - await expect(manage1.selectedNodeTypeSelect).toHaveValue('g1') - await manage1.close() - await expect(manage1.root).toBeHidden() - - const manage2 = await group2.manageGroupNode() - await expect(manage2.selectedNodeTypeSelect).toHaveValue('g2') + await comfyPage.workflow.loadWorkflow('groupnodes/legacy_group_node') + await expect.poll(() => comfyPage.nodeOps.getGraphNodesCount()).toBe(1) + await expect( + comfyPage.page.getByTestId(TestIds.dialogs.errorOverlay) + ).toBeHidden() }) test('Preserves hidden input configuration when containing duplicate node types', async ({ @@ -201,50 +67,20 @@ test.describe('Group Node', { tag: '@node' }, () => { .toBe(2) }) - test('Reconnects inputs after configuration changed via manage dialog save', async ({ + test('Manage Group Node dialog opens for an existing group node', async ({ comfyPage }) => { - const expectSingleNode = async (type: string) => { - const nodes = await comfyPage.nodeOps.getNodeRefsByType(type) - expect(nodes).toHaveLength(1) - return nodes[0] - } - const latent = await expectSingleNode('EmptyLatentImage') - const sampler = await expectSingleNode('KSampler') - // Remove existing link - const samplerInput = await sampler.getInput(0) - await samplerInput.removeLinks() - // Group latent + sampler - await latent.click('title', { - modifiers: ['Shift'] - }) - await sampler.click('title', { - modifiers: ['Shift'] - }) - const groupNode = await sampler.convertToGroupNode() - // Connect node to group - const ckpt = await expectSingleNode('CheckpointLoaderSimple') - const input = await ckpt.connectOutput(0, groupNode, 0) - await expect.poll(() => input.getLinkCount()).toBe(1) - // Modify the group node via manage dialog - const manage = await groupNode.manageGroupNode() - await manage.selectNode('KSampler') - await manage.changeTab('Inputs') - await manage.setLabel('model', 'test') - await manage.save() - await manage.close() - // Ensure the link is still present - await expect.poll(() => input.getLinkCount()).toBe(1) - }) + await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top') + await comfyPage.workflow.loadWorkflow('groupnodes/group_node_v1.3.3') - test('Loads from a workflow using the legacy path separator ("/")', async ({ - comfyPage - }) => { - await comfyPage.workflow.loadWorkflow('groupnodes/legacy_group_node') - await expect.poll(() => comfyPage.nodeOps.getGraphNodesCount()).toBe(1) - await expect( - comfyPage.page.getByTestId(TestIds.dialogs.errorOverlay) - ).toBeHidden() + const groupNode = await comfyPage.nodeOps.getFirstNodeRef() + if (!groupNode) throw new Error('Group node not found in workflow') + + const manage = await groupNode.manageGroupNode() + await comfyPage.nextFrame() + await expect(manage.selectedNodeTypeSelect).toHaveValue('group_node') + await manage.close() + await expect(manage.root).toBeHidden() }) test.describe('Copy and paste', () => { @@ -299,12 +135,8 @@ test.describe('Group Node', { tag: '@node' }, () => { test('Copies and pastes group node after clearing workflow', async ({ comfyPage }) => { - // Set setting await comfyPage.settings.setSetting('Comfy.ConfirmClear', false) - - // Clear workflow await comfyPage.command.executeCommand('Comfy.ClearWorkflow') - await comfyPage.clipboard.paste() await verifyNodeLoaded(comfyPage, 1) }) @@ -342,22 +174,4 @@ test.describe('Group Node', { tag: '@node' }, () => { }) }) }) - - test.describe('Keybindings', () => { - test('Convert to group node, no selection', async ({ comfyPage }) => { - await expect(comfyPage.toast.visibleToasts).toHaveCount(0) - await comfyPage.page.keyboard.press('Alt+g') - await expect(comfyPage.toast.visibleToasts).toHaveCount(1) - }) - - test('Convert to group node, selected 1 node', async ({ comfyPage }) => { - await expect(comfyPage.toast.visibleToasts).toHaveCount(0) - await comfyPage.canvas.click({ - position: DefaultGraphPositions.textEncodeNode1 - }) - await comfyPage.nextFrame() - await comfyPage.page.keyboard.press('Alt+g') - await expect(comfyPage.toast.visibleToasts).toHaveCount(1) - }) - }) }) diff --git a/browser_tests/tests/rightClickMenu.spec.ts b/browser_tests/tests/rightClickMenu.spec.ts index 49f39860e1..d6d65cb905 100644 --- a/browser_tests/tests/rightClickMenu.spec.ts +++ b/browser_tests/tests/rightClickMenu.spec.ts @@ -35,23 +35,6 @@ test.describe( 'add-group-group-added.png' ) }) - - test('Can convert to group node', async ({ comfyPage }) => { - await comfyPage.nodeOps.selectNodes(['CLIP Text Encode (Prompt)']) - await expect(comfyPage.canvas).toHaveScreenshot('selected-2-nodes.png') - await comfyPage.canvasOps.rightClick() - await comfyPage.contextMenu.clickMenuItem( - 'Convert to Group Node (Deprecated)' - ) - await comfyPage.contextMenu.waitForHidden() - await comfyPage.nodeOps.promptDialogInput.fill('GroupNode2CLIP') - await comfyPage.page.keyboard.press('Enter') - await comfyPage.nodeOps.promptDialogInput.waitFor({ state: 'hidden' }) - await comfyPage.expectScreenshot( - comfyPage.canvas, - 'right-click-node-group-node.png' - ) - }) } ) diff --git a/browser_tests/tests/vueNodes/interactions/node/contextMenu.spec.ts b/browser_tests/tests/vueNodes/interactions/node/contextMenu.spec.ts index 91d90f3481..65be2f9ab4 100644 --- a/browser_tests/tests/vueNodes/interactions/node/contextMenu.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/node/contextMenu.spec.ts @@ -507,25 +507,6 @@ test.describe('Vue Node Context Menu', { tag: '@vue-nodes' }, () => { .toBe(initialGroupCount + 1) }) - test('should convert to group node via context menu', async ({ - comfyPage - }) => { - await openMultiNodeContextMenu(comfyPage, nodeTitles) - await clickExactMenuItem(comfyPage, 'Convert to Group Node') - - await comfyPage.nodeOps.promptDialogInput.waitFor({ state: 'visible' }) - await comfyPage.nodeOps.fillPromptDialog('TestGroupNode') - - await expect - .poll(async () => { - const groupNodes = await comfyPage.nodeOps.getNodeRefsByType( - 'workflow>TestGroupNode' - ) - return groupNodes.length - }) - .toBe(1) - }) - test('should convert selected nodes to subgraph via context menu', async ({ comfyPage }) => { diff --git a/src/composables/graph/contextMenuConverter.ts b/src/composables/graph/contextMenuConverter.ts index cf866a096f..6fbee00b81 100644 --- a/src/composables/graph/contextMenuConverter.ts +++ b/src/composables/graph/contextMenuConverter.ts @@ -245,9 +245,7 @@ const MENU_ORDER: string[] = [ 'Paste Image', 'Save Image', 'Copy (Clipspace)', - 'Paste (Clipspace)', - // Fallback for other core items - 'Convert to Group Node (Deprecated)' + 'Paste (Clipspace)' ] /** diff --git a/src/composables/graph/useMoreOptionsMenu.ts b/src/composables/graph/useMoreOptionsMenu.ts index 0ce59386de..f2a1556c64 100644 --- a/src/composables/graph/useMoreOptionsMenu.ts +++ b/src/composables/graph/useMoreOptionsMenu.ts @@ -45,8 +45,7 @@ export interface SubMenuOption { } export enum BadgeVariant { - NEW = 'new', - DEPRECATED = 'deprecated' + NEW = 'new' } // Global singleton for NodeOptions component reference diff --git a/src/composables/graph/useSelectionMenuOptions.test.ts b/src/composables/graph/useSelectionMenuOptions.test.ts index b35544e87d..57c83d48b5 100644 --- a/src/composables/graph/useSelectionMenuOptions.test.ts +++ b/src/composables/graph/useSelectionMenuOptions.test.ts @@ -72,14 +72,14 @@ describe('useSelectionMenuOptions - multiple nodes options', () => { expect(mocks.frameNodes).toHaveBeenCalledOnce() }) - it('returns Convert to Group Node option from getMultipleNodesOptions', () => { + it('does not include a Convert to Group Node option', () => { const { getMultipleNodesOptions } = useSelectionMenuOptions() const options = getMultipleNodesOptions() const groupNodeOption = options.find( (opt) => opt.label === 'contextMenu.Convert to Group Node' ) - expect(groupNodeOption).toBeDefined() + expect(groupNodeOption).toBeUndefined() }) }) diff --git a/src/composables/graph/useSelectionMenuOptions.ts b/src/composables/graph/useSelectionMenuOptions.ts index b571f9ab5d..a9fa8c63c2 100644 --- a/src/composables/graph/useSelectionMenuOptions.ts +++ b/src/composables/graph/useSelectionMenuOptions.ts @@ -1,8 +1,6 @@ import { computed } from 'vue' import { useI18n } from 'vue-i18n' -import { useCommandStore } from '@/stores/commandStore' - import { useFrameNodes } from './useFrameNodes' import { BadgeVariant } from './useMoreOptionsMenu' import type { MenuOption } from './useMoreOptionsMenu' @@ -102,28 +100,13 @@ export function useSelectionMenuOptions() { return options } - const getMultipleNodesOptions = (): MenuOption[] => { - const convertToGroupNodes = () => { - const commandStore = useCommandStore() - void commandStore.execute( - 'Comfy.GroupNode.ConvertSelectedNodesToGroupNode' - ) + const getMultipleNodesOptions = (): MenuOption[] => [ + { + label: t('g.frameNodes'), + icon: 'icon-[lucide--frame]', + action: frameNodes } - - return [ - { - label: t('contextMenu.Convert to Group Node'), - icon: 'icon-[lucide--group]', - action: convertToGroupNodes, - badge: BadgeVariant.DEPRECATED - }, - { - label: t('g.frameNodes'), - icon: 'icon-[lucide--frame]', - action: frameNodes - } - ] - } + ] const getAlignmentOptions = (): MenuOption[] => [ { diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index aec52616c8..b12dadc649 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -1833,38 +1833,6 @@ const replaceLegacySeparators = (nodes: ComfyNode[]): void => { } } -/** - * Convert selected nodes to a group node - * @throws {Error} if no nodes are selected - * @throws {Error} if a group node is already selected - * @throws {Error} if a group node is selected - * - * The context menu item should not be available if any of the above conditions are met. - * The error is automatically handled by the commandStore when the command is executed. - */ -async function convertSelectedNodesToGroupNode() { - const nodes = Object.values(app.canvas.selected_nodes ?? {}) - if (nodes.length === 0) { - throw new Error('No nodes selected') - } - if (nodes.length === 1) { - throw new Error('Please select multiple nodes to convert to group node') - } - - for (const node of nodes) { - if (node instanceof SubgraphNode) { - throw new Error('Selected nodes contain a subgraph node') - } - if (GroupNodeHandler.isGroupNode(node)) { - throw new Error('Selected nodes contain a group node') - } - } - return await GroupNodeHandler.fromNodes(nodes) -} - -const convertDisabled = (selected: LGraphNode[]) => - selected.length < 2 || !!selected.find((n) => GroupNodeHandler.isGroupNode(n)) - function ungroupSelectedGroupNodes() { const nodes = Object.values(app.canvas.selected_nodes ?? {}) for (const node of nodes) { @@ -1900,13 +1868,6 @@ let globalDefs: Record const ext: ComfyExtension = { name: id, commands: [ - { - id: 'Comfy.GroupNode.ConvertSelectedNodesToGroupNode', - label: 'Convert selected nodes to group node', - icon: 'pi pi-sitemap', - versionAdded: '1.3.17', - function: () => convertSelectedNodesToGroupNode() - }, { id: 'Comfy.GroupNode.UngroupSelectedGroupNodes', label: 'Ungroup selected group nodes', @@ -1924,13 +1885,6 @@ const ext: ComfyExtension = { } ], keybindings: [ - { - commandId: 'Comfy.GroupNode.ConvertSelectedNodesToGroupNode', - combo: { - alt: true, - key: 'g' - } - }, { commandId: 'Comfy.GroupNode.UngroupSelectedGroupNodes', combo: { @@ -1942,42 +1896,13 @@ const ext: ComfyExtension = { ], getCanvasMenuItems(canvas): IContextMenuValue[] { - const items: IContextMenuValue[] = [] - const selected = Object.values(canvas.selected_nodes ?? {}) - const convertEnabled = !convertDisabled(selected) - - items.push({ - content: `Convert to Group Node (Deprecated)`, - disabled: !convertEnabled, - // @ts-expect-error async callback - legacy menu API doesn't expect Promise - callback: async () => convertSelectedNodesToGroupNode() - }) - const groups = canvas.graph?.extra?.groupNodes const manageDisabled = !groups || !Object.keys(groups).length - items.push({ - content: `Manage Group Nodes`, - disabled: manageDisabled, - callback: () => manageGroupNodes() - }) - - return items - }, - - getNodeMenuItems(node): IContextMenuValue[] { - if (GroupNodeHandler.isGroupNode(node)) { - return [] - } - - const selected = Object.values(app.canvas.selected_nodes ?? {}) - const convertEnabled = !convertDisabled(selected) - return [ { - content: `Convert to Group Node (Deprecated)`, - disabled: !convertEnabled, - // @ts-expect-error async callback - legacy menu API doesn't expect Promise - callback: async () => convertSelectedNodesToGroupNode() + content: `Manage Group Nodes`, + disabled: manageDisabled, + callback: () => manageGroupNodes() } ] }, diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index cda6d1e78c..a452a4bf4d 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -158,9 +158,6 @@ "Comfy_Graph_UnpackSubgraph": { "label": "Unpack the selected Subgraph" }, - "Comfy_GroupNode_ConvertSelectedNodesToGroupNode": { - "label": "Convert selected nodes to group node" - }, "Comfy_GroupNode_ManageGroupNodes": { "label": "Manage group nodes" }, diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 3d4cc8dd96..5cff98880c 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -584,7 +584,6 @@ "Copy (Clipspace)": "Copy (Clipspace)", "Add Node": "Add Node", "Add Group": "Add Group", - "Convert to Group Node": "Convert to Group Node", "Manage Group Nodes": "Manage Group Nodes", "Add Group For Selected Nodes": "Add Group For Selected Nodes", "Save Selected as Template": "Save Selected as Template", @@ -1381,7 +1380,6 @@ "Group Selected Nodes": "Group Selected Nodes", "Toggle promotion of hovered widget": "Toggle promotion of hovered widget", "Unpack the selected Subgraph": "Unpack the selected Subgraph", - "Convert selected nodes to group node": "Convert selected nodes to group node", "Manage group nodes": "Manage group nodes", "Ungroup selected group nodes": "Ungroup selected group nodes", "About ComfyUI": "About ComfyUI",