mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Audit all skipped/fixme tests: delete stale tests whose underlying features were removed, re-enable tests that pass with minimal fixes, and remove orphaned production code that only the deleted tests exercised. Net result: **−2,350 lines** across 50 files. ## Changes - **Pruned stale skipped tests** (entire files deleted): - `LGraph.configure.test.ts`, `LGraph.constructor.test.ts` — tested removed LGraph constructor paths - `LGraphCanvas.ghostAutoPan.test.ts`, `LGraphCanvas.linkDragAutoPan.test.ts`, `useAutoPan.test.ts`, `useSlotLinkInteraction.autoPan.test.ts` — tested removed auto-pan feature - `useNodePointerInteractions.test.ts` — single skipped test for removed callback - `ImageLightbox.test.ts` — component replaced by `MediaLightbox` - `appModeWidgetRename.spec.ts` (E2E) — feature removed; helper `AppModeHelper.ts` also deleted - `domWidget.spec.ts`, `widget.spec.ts` (E2E) — tested removed widget behavior - **Removed orphaned production code** surfaced by test pruning: - `useAutoPan.ts` — composable + 93 lines of auto-pan logic in `LGraphCanvas.ts` - `ImageLightbox.vue` — replaced by `MediaLightbox` - Auto-pan integration in `useSlotLinkInteraction.ts` and `useNodeDrag.ts` - Dead settings (`LinkSnapping.AutoPanSpeed`, `LinkSnapping.AutoPanMargin`) in `coreSettings.ts` and `useLitegraphSettings.ts` - Unused subgraph methods (`SubgraphNode.getExposedInput`, `SubgraphInput.getParentInput`) - Dead i18n key, dead API schema field, dead fixture exports (`dirtyTest`, `basicSerialisableGraph`) - Dead test utility `litegraphTestUtils.ts` - **Re-enabled skipped tests with minimal fixes**: - `useBrowserTabTitle.test.ts` — removed skip, test passes as-is - `eventUtils.test.ts` — replaced MSW dependency with direct `fetch` mock - `SubscriptionPanel.test.ts` — stabilized button selectors, timezone-safe date assertion - `LinkConnector.test.ts` — removed stale describe blocks, kept passing suite - `widgetUtil.test.ts` — removed skipped tests for deleted functionality - `comfyManagerStore.test.ts` — removed skipped `isPackInstalling` / `action buttons` / `loading states` blocks - **Re-enabled then re-skipped 3 flaky E2E tests** (fail in CI for pre-existing reasons): - `browserTabTitle.spec.ts` — canvas click timeout (element not visible) - `groupNode.spec.ts` — screenshot diff (stale golden image) - `nodeSearchBox.spec.ts` — `p-dialog-mask` intercepts pointer events - **Simplified production code** alongside test cleanup: - `useNodeDrag.ts` — removed auto-pan integration, simplified from 170→100 lines - `DropZone.vue` — refactored URL-drop handling, removed unused code path - `ToInputFromIoNodeLink.ts`, `SubgraphInputEventMap.ts` — removed dead subgraph wiring - **Dependencies**: none - **Breaking**: none (all removed code was internal/unused) ## Review Focus - Confirm deleted production code (`useAutoPan`, `ImageLightbox`, subgraph methods) has no remaining callers - Validate that simplified `useNodeDrag.ts` preserves drag behavior without auto-pan - Check that re-skipped E2E tests have clear skip reasons for future triage ## Screenshots (if applicable) N/A --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: github-actions <github-actions@github.com>
379 lines
12 KiB
TypeScript
379 lines
12 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
|
|
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
|
import { TestIds } from '../../fixtures/selectors'
|
|
|
|
test.describe('Workflows sidebar', () => {
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
|
await comfyPage.settings.setSetting(
|
|
'Comfy.Workflow.WorkflowTabsPosition',
|
|
'Sidebar'
|
|
)
|
|
|
|
// Open the sidebar
|
|
const tab = comfyPage.menu.workflowsTab
|
|
await tab.open()
|
|
})
|
|
|
|
test.afterEach(async ({ comfyPage }) => {
|
|
await comfyPage.workflow.setupWorkflowsDirectory({})
|
|
})
|
|
|
|
test('Can create new blank workflow', async ({ comfyPage }) => {
|
|
const tab = comfyPage.menu.workflowsTab
|
|
expect(await tab.getOpenedWorkflowNames()).toEqual(['*Unsaved Workflow'])
|
|
|
|
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
|
|
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
|
'*Unsaved Workflow',
|
|
'*Unsaved Workflow (2)'
|
|
])
|
|
})
|
|
|
|
test('Can show top level saved workflows', async ({ comfyPage }) => {
|
|
await comfyPage.workflow.setupWorkflowsDirectory({
|
|
'workflow1.json': 'default.json',
|
|
'workflow2.json': 'default.json'
|
|
})
|
|
|
|
const tab = comfyPage.menu.workflowsTab
|
|
await tab.open()
|
|
expect(await tab.getTopLevelSavedWorkflowNames()).toEqual(
|
|
expect.arrayContaining(['workflow1', 'workflow2'])
|
|
)
|
|
})
|
|
|
|
test('Can duplicate workflow', async ({ comfyPage }) => {
|
|
const tab = comfyPage.menu.workflowsTab
|
|
await comfyPage.menu.topbar.saveWorkflow('workflow1')
|
|
|
|
expect(await tab.getTopLevelSavedWorkflowNames()).toEqual(
|
|
expect.arrayContaining(['workflow1'])
|
|
)
|
|
|
|
await comfyPage.command.executeCommand('Comfy.DuplicateWorkflow')
|
|
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
|
'workflow1',
|
|
'*workflow1 (Copy)'
|
|
])
|
|
|
|
await comfyPage.command.executeCommand('Comfy.DuplicateWorkflow')
|
|
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
|
'workflow1',
|
|
'*workflow1 (Copy)',
|
|
'*workflow1 (Copy) (2)'
|
|
])
|
|
|
|
await comfyPage.command.executeCommand('Comfy.DuplicateWorkflow')
|
|
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
|
'workflow1',
|
|
'*workflow1 (Copy)',
|
|
'*workflow1 (Copy) (2)',
|
|
'*workflow1 (Copy) (3)'
|
|
])
|
|
})
|
|
|
|
test('Can open workflow after insert', async ({ comfyPage }) => {
|
|
await comfyPage.workflow.setupWorkflowsDirectory({
|
|
'workflow1.json': 'nodes/single_ksampler.json'
|
|
})
|
|
|
|
const tab = comfyPage.menu.workflowsTab
|
|
await tab.open()
|
|
await comfyPage.command.executeCommand('Comfy.LoadDefaultWorkflow')
|
|
const originalNodeCount = await comfyPage.nodeOps.getNodeCount()
|
|
|
|
await tab.insertWorkflow(tab.getPersistedItem('workflow1'))
|
|
await expect
|
|
.poll(() => comfyPage.nodeOps.getNodeCount())
|
|
.toEqual(originalNodeCount + 1)
|
|
|
|
await tab.getPersistedItem('workflow1').click()
|
|
await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toEqual(1)
|
|
})
|
|
|
|
test('Can rename nested workflow from opened workflow item', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.setupWorkflowsDirectory({
|
|
foo: {
|
|
'bar.json': 'default.json'
|
|
}
|
|
})
|
|
|
|
const tab = comfyPage.menu.workflowsTab
|
|
await tab.open()
|
|
// Switch to the parent folder
|
|
await tab.getPersistedItem('foo').click()
|
|
// Switch to the nested workflow
|
|
await tab.getPersistedItem('bar').click()
|
|
|
|
const openedWorkflow = tab.getOpenedItem('foo/bar')
|
|
await tab.renameWorkflow(openedWorkflow, 'foo/baz')
|
|
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
|
'*Unsaved Workflow',
|
|
'foo/baz'
|
|
])
|
|
})
|
|
|
|
test('Can save workflow as', async ({ comfyPage }) => {
|
|
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
|
|
await comfyPage.menu.topbar.saveWorkflowAs('workflow3')
|
|
await expect
|
|
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
|
|
.toEqual(['*Unsaved Workflow', 'workflow3'])
|
|
|
|
await comfyPage.menu.topbar.saveWorkflowAs('workflow4')
|
|
await expect
|
|
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
|
|
.toEqual(['*Unsaved Workflow', 'workflow3', 'workflow4'])
|
|
})
|
|
|
|
test('Exported workflow does not contain localized slot names', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.loadWorkflow('default')
|
|
const exportedWorkflow = await comfyPage.workflow.getExportedWorkflow({
|
|
api: false
|
|
})
|
|
expect(exportedWorkflow).toBeDefined()
|
|
for (const node of exportedWorkflow.nodes) {
|
|
for (const slot of node.inputs ?? []) {
|
|
expect(slot.localized_name).toBeUndefined()
|
|
expect(slot.label).toBeUndefined()
|
|
}
|
|
for (const slot of node.outputs ?? []) {
|
|
expect(slot.localized_name).toBeUndefined()
|
|
expect(slot.label).toBeUndefined()
|
|
}
|
|
}
|
|
})
|
|
|
|
test('Can export same workflow with different locales', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.workflow.loadWorkflow('default')
|
|
|
|
// Setup download listener before triggering the export
|
|
const downloadPromise = comfyPage.page.waitForEvent('download')
|
|
await comfyPage.menu.topbar.exportWorkflow('exported_default.json')
|
|
|
|
// Wait for the download and get the file content
|
|
const download = await downloadPromise
|
|
expect(download.suggestedFilename()).toBe('exported_default.json')
|
|
|
|
// Get the exported workflow content
|
|
const downloadedContent = await comfyPage.workflow.getExportedWorkflow({
|
|
api: false
|
|
})
|
|
|
|
await comfyPage.settings.setSetting('Comfy.Locale', 'zh')
|
|
await comfyPage.setup()
|
|
|
|
const downloadedContentZh = await comfyPage.workflow.getExportedWorkflow({
|
|
api: false
|
|
})
|
|
|
|
// Compare the exported workflow with the original
|
|
delete downloadedContent.id
|
|
delete downloadedContentZh.id
|
|
expect(downloadedContent).toBeDefined()
|
|
expect(downloadedContent).toEqual(downloadedContentZh)
|
|
})
|
|
|
|
test('Can save workflow as with same name', async ({ comfyPage }) => {
|
|
await comfyPage.menu.topbar.saveWorkflow('workflow5')
|
|
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
|
|
'workflow5'
|
|
])
|
|
|
|
await comfyPage.menu.topbar.saveWorkflowAs('workflow5')
|
|
await comfyPage.confirmDialog.click('overwrite')
|
|
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
|
|
'workflow5'
|
|
])
|
|
})
|
|
|
|
test('Can save temporary workflow with unmodified name', async ({
|
|
comfyPage
|
|
}) => {
|
|
expect(await comfyPage.workflow.isCurrentWorkflowModified()).toBe(false)
|
|
|
|
await comfyPage.menu.topbar.saveWorkflow('Unsaved Workflow')
|
|
// Should not trigger the overwrite dialog
|
|
expect(
|
|
await comfyPage.page.locator('.comfy-modal-content:visible').count()
|
|
).toBe(0)
|
|
|
|
expect(await comfyPage.workflow.isCurrentWorkflowModified()).toBe(false)
|
|
})
|
|
|
|
test('Can overwrite other workflows with save as', async ({ comfyPage }) => {
|
|
const topbar = comfyPage.menu.topbar
|
|
await topbar.saveWorkflow('workflow1')
|
|
await topbar.saveWorkflowAs('workflow2')
|
|
await comfyPage.nextFrame()
|
|
await expect
|
|
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
|
|
.toEqual(['workflow1', 'workflow2'])
|
|
await expect
|
|
.poll(() => comfyPage.menu.workflowsTab.getActiveWorkflowName())
|
|
.toEqual('workflow2')
|
|
|
|
await topbar.saveWorkflowAs('workflow1')
|
|
await comfyPage.confirmDialog.click('overwrite')
|
|
// The old workflow1 should be deleted and the new one should be saved.
|
|
await expect
|
|
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames())
|
|
.toEqual(['workflow2', 'workflow1'])
|
|
await expect
|
|
.poll(() => comfyPage.menu.workflowsTab.getActiveWorkflowName())
|
|
.toEqual('workflow1')
|
|
})
|
|
|
|
test('Reports missing nodes warning again when switching back to workflow', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.settings.setSetting(
|
|
'Comfy.RightSidePanel.ShowErrorsTab',
|
|
true
|
|
)
|
|
await comfyPage.workflow.loadWorkflow('missing/missing_nodes')
|
|
|
|
const errorOverlay = comfyPage.page.getByTestId(
|
|
TestIds.dialogs.errorOverlay
|
|
)
|
|
await expect(errorOverlay).toBeVisible()
|
|
|
|
// Dismiss the error overlay
|
|
await errorOverlay.getByTestId(TestIds.dialogs.errorOverlayDismiss).click()
|
|
await expect(errorOverlay).not.toBeVisible()
|
|
|
|
// Load blank workflow
|
|
await comfyPage.menu.workflowsTab.open()
|
|
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
|
|
|
|
// Switch back to the missing_nodes workflow — overlay should reappear
|
|
// so users can install missing node packs without a page reload
|
|
await comfyPage.menu.workflowsTab.switchToWorkflow('missing_nodes')
|
|
|
|
await expect(errorOverlay).toBeVisible()
|
|
})
|
|
|
|
test('Can close saved-workflows from the open workflows section', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.menu.topbar.saveWorkflow(
|
|
`tempWorkflow-${test.info().title}`
|
|
)
|
|
const closeButton = comfyPage.page.locator(
|
|
'.comfyui-workflows-open .close-workflow-button'
|
|
)
|
|
await closeButton.click()
|
|
await expect
|
|
.poll(() => comfyPage.menu.workflowsTab.getOpenedWorkflowNames(), {
|
|
timeout: 5000
|
|
})
|
|
.toEqual(['*Unsaved Workflow'])
|
|
})
|
|
|
|
test('Can close saved workflow with command', async ({ comfyPage }) => {
|
|
const tab = comfyPage.menu.workflowsTab
|
|
await comfyPage.menu.topbar.saveWorkflow('workflow1')
|
|
await comfyPage.command.executeCommand('Workspace.CloseWorkflow')
|
|
expect(await tab.getOpenedWorkflowNames()).toEqual(['*Unsaved Workflow'])
|
|
})
|
|
|
|
test('Can delete workflows (confirm disabled)', async ({ comfyPage }) => {
|
|
await comfyPage.settings.setSetting('Comfy.Workflow.ConfirmDelete', false)
|
|
|
|
const { topbar, workflowsTab } = comfyPage.menu
|
|
|
|
const filename = 'workflow18'
|
|
await topbar.saveWorkflow(filename)
|
|
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
|
|
|
|
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
|
|
await comfyPage.nextFrame()
|
|
await comfyPage.contextMenu.clickMenuItem('Delete')
|
|
await comfyPage.nextFrame()
|
|
|
|
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
|
|
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
|
|
'*Unsaved Workflow'
|
|
])
|
|
})
|
|
|
|
test('Can delete workflows', async ({ comfyPage }) => {
|
|
const { topbar, workflowsTab } = comfyPage.menu
|
|
|
|
const filename = 'workflow18'
|
|
await topbar.saveWorkflow(filename)
|
|
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
|
|
|
|
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
|
|
await comfyPage.contextMenu.clickMenuItem('Delete')
|
|
await comfyPage.nextFrame()
|
|
|
|
await comfyPage.confirmDialog.click('delete')
|
|
|
|
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
|
|
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
|
|
'*Unsaved Workflow'
|
|
])
|
|
})
|
|
|
|
test('Can duplicate workflow from context menu', async ({ comfyPage }) => {
|
|
await comfyPage.workflow.setupWorkflowsDirectory({
|
|
'workflow1.json': 'default.json'
|
|
})
|
|
|
|
const { workflowsTab } = comfyPage.menu
|
|
await workflowsTab.open()
|
|
|
|
await workflowsTab.getPersistedItem('workflow1').click({ button: 'right' })
|
|
await comfyPage.contextMenu.clickMenuItem('Duplicate')
|
|
await expect
|
|
.poll(() => workflowsTab.getOpenedWorkflowNames())
|
|
.toEqual(['*Unsaved Workflow', '*workflow1 (Copy)'])
|
|
})
|
|
|
|
test('Can drop workflow from workflows sidebar', async ({ comfyPage }) => {
|
|
await comfyPage.workflow.setupWorkflowsDirectory({
|
|
'workflow1.json': 'default.json'
|
|
})
|
|
|
|
await comfyPage.menu.workflowsTab.open()
|
|
|
|
// Wait for workflow to appear in Browse section after sync
|
|
const workflowItem =
|
|
comfyPage.menu.workflowsTab.getPersistedItem('workflow1')
|
|
await expect(workflowItem).toBeVisible({ timeout: 3000 })
|
|
|
|
const nodeCount = await comfyPage.nodeOps.getGraphNodesCount()
|
|
|
|
// Get the bounding box of the canvas element
|
|
const canvasBoundingBox = (await comfyPage.page
|
|
.locator('#graph-canvas')
|
|
.boundingBox())!
|
|
|
|
// Calculate the center position of the canvas
|
|
const targetPosition = {
|
|
x: canvasBoundingBox.x + canvasBoundingBox.width / 2,
|
|
y: canvasBoundingBox.y + canvasBoundingBox.height / 2
|
|
}
|
|
|
|
await comfyPage.page.dragAndDrop(
|
|
'.comfyui-workflows-browse .node-label:has-text("workflow1")',
|
|
'#graph-canvas',
|
|
{ targetPosition }
|
|
)
|
|
|
|
// Wait for nodes to be inserted after drag-drop with retryable assertion
|
|
await expect
|
|
.poll(() => comfyPage.nodeOps.getGraphNodesCount(), { timeout: 3000 })
|
|
.toBe(nodeCount * 2)
|
|
})
|
|
})
|