import { comfyPageFixture as test, comfyExpect as expect } from '@e2e/fixtures/ComfyPage' import { mockNodeReplacements, mockNodeReplacementsSingle } from '@e2e/fixtures/data/nodeReplacements' import { loadWorkflowAndOpenErrorsTab } from '@e2e/fixtures/helpers/ErrorsTabHelper' import { getSwapNodesGroup, setupNodeReplacement } from '@e2e/fixtures/helpers/NodeReplacementHelper' const renderModes = [ { name: 'vue nodes', vueNodesEnabled: true }, { name: 'litegraph', vueNodesEnabled: false } ] as const test.describe('Node replacement', { tag: ['@node', '@ui'] }, () => { for (const mode of renderModes) { test.describe(`(${mode.name})`, () => { test.describe('Single replacement', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.settings.setSetting( 'Comfy.VueNodes.Enabled', mode.vueNodesEnabled ) await setupNodeReplacement(comfyPage, mockNodeReplacementsSingle) await loadWorkflowAndOpenErrorsTab( comfyPage, 'missing/node_replacement_simple' ) }) test('Swap Nodes group appears in errors tab for replaceable nodes', async ({ comfyPage }) => { const swapGroup = getSwapNodesGroup(comfyPage.page) await expect(swapGroup).toBeVisible() await expect(swapGroup).toContainText('E2E_OldSampler') await expect( swapGroup.getByRole('button', { name: 'Replace All', exact: true }) ).toBeVisible() }) test('Replace Node replaces a single group in-place', async ({ comfyPage }) => { const swapGroup = getSwapNodesGroup(comfyPage.page) await swapGroup.getByRole('button', { name: /replace node/i }).click() await expect(swapGroup).toBeHidden() const workflow = await comfyPage.workflow.getExportedWorkflow() expect( workflow.nodes, 'Node count should be unchanged after in-place replacement' ).toHaveLength(2) const nodeTypes = workflow.nodes.map((n) => n.type) expect(nodeTypes).not.toContain('E2E_OldSampler') expect(nodeTypes).toContain('KSampler') const ksampler = workflow.nodes.find((n) => n.type === 'KSampler') expect( ksampler?.id, 'Replaced node should keep the original id' ).toBe(1) const linkFromReplacedToDecode = workflow.links?.find( (l) => l[1] === 1 && l[3] === 2 ) expect( linkFromReplacedToDecode, 'Output link from replaced node to VAEDecode should be preserved' ).toBeDefined() }) test('Widget values are preserved after replacement', async ({ comfyPage }) => { await getSwapNodesGroup(comfyPage.page) .getByRole('button', { name: /replace node/i }) .click() const workflow = await comfyPage.workflow.getExportedWorkflow() const ksampler = workflow.nodes.find((n) => n.type === 'KSampler') expect(ksampler?.widgets_values).toBeDefined() const widgetValues = ksampler!.widgets_values as unknown[] expect(widgetValues).toEqual([ 42, 'randomize', 20, 7, 'euler', 'normal', 1 ]) }) test('Success toast is shown after replacement', async ({ comfyPage }) => { await getSwapNodesGroup(comfyPage.page) .getByRole('button', { name: /replace node/i }) .click() await expect(comfyPage.visibleToasts.first()).toContainText( /replaced|swapped/i ) }) }) test.describe('Multi-type replacement', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.settings.setSetting( 'Comfy.VueNodes.Enabled', mode.vueNodesEnabled ) await setupNodeReplacement(comfyPage, mockNodeReplacements) await loadWorkflowAndOpenErrorsTab( comfyPage, 'missing/node_replacement_multi' ) }) test('Replace All replaces all groups across multiple types', async ({ comfyPage }) => { const swapGroup = getSwapNodesGroup(comfyPage.page) await expect(swapGroup).toBeVisible() await expect(swapGroup).toContainText('E2E_OldSampler') await expect(swapGroup).toContainText('E2E_OldUpscaler') await swapGroup .getByRole('button', { name: 'Replace All', exact: true }) .click() await expect(swapGroup).toBeHidden() const workflow = await comfyPage.workflow.getExportedWorkflow() const nodeTypes = workflow.nodes.map((n) => n.type) expect(nodeTypes).not.toContain('E2E_OldSampler') expect(nodeTypes).not.toContain('E2E_OldUpscaler') expect(nodeTypes).toContain('KSampler') expect(nodeTypes).toContain('ImageScaleBy') }) test('Output connections are preserved across replacement with output mapping', async ({ comfyPage }) => { await getSwapNodesGroup(comfyPage.page) .getByRole('button', { name: 'Replace All', exact: true }) .click() const replacedNodeOutputLinkCount = await comfyPage.page.evaluate( () => window.app!.graph!.getNodeById(2)?.outputs[0]?.links?.length ?? 0 ) expect( replacedNodeOutputLinkCount, 'Replaced upscaler should still drive its downstream consumer' ).toBeGreaterThan(0) }) }) }) } })