mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-14 19:51:05 +00:00
Compare commits
9 Commits
docs/weekl
...
feat/node-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56da748234 | ||
|
|
2524846f5c | ||
|
|
12f578870e | ||
|
|
72eed86cea | ||
|
|
719ed16d32 | ||
|
|
5899a9392e | ||
|
|
e39468567a | ||
|
|
a373633ab2 | ||
|
|
521019d173 |
34
browser_tests/assets/missing/missing_media_bypassed.json
Normal file
34
browser_tests/assets/missing/missing_media_bypassed.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"last_node_id": 10,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 10,
|
||||
"type": "LoadImage",
|
||||
"pos": [50, 200],
|
||||
"size": [315, 314],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 4,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{ "name": "IMAGE", "type": "IMAGE", "links": null },
|
||||
{ "name": "MASK", "type": "MASK", "links": null }
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": ["nonexistent_test_image_12345.png", "image"]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
{
|
||||
"id": 10,
|
||||
"type": "LoadImage",
|
||||
"pos": [50, 50],
|
||||
"pos": [50, 200],
|
||||
"size": [315, 314],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
@@ -31,7 +31,7 @@
|
||||
{
|
||||
"id": 11,
|
||||
"type": "LoadImage",
|
||||
"pos": [450, 50],
|
||||
"pos": [450, 200],
|
||||
"size": [315, 314],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{
|
||||
"id": 10,
|
||||
"type": "LoadImage",
|
||||
"pos": [50, 50],
|
||||
"pos": [50, 200],
|
||||
"size": [315, 314],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
|
||||
@@ -1,7 +1,27 @@
|
||||
{
|
||||
"last_node_id": 0,
|
||||
"last_node_id": 1,
|
||||
"last_link_id": 0,
|
||||
"nodes": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [256, 256],
|
||||
"size": [315, 98],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{ "name": "MODEL", "type": "MODEL", "links": null },
|
||||
{ "name": "CLIP", "type": "CLIP", "links": null },
|
||||
{ "name": "VAE", "type": "VAE", "links": null }
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": ["fake_model.safetensors"]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
@@ -15,7 +35,7 @@
|
||||
{
|
||||
"name": "fake_model.safetensors",
|
||||
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
|
||||
"directory": "text_encoders"
|
||||
"directory": "checkpoints"
|
||||
}
|
||||
],
|
||||
"version": 0.4
|
||||
|
||||
42
browser_tests/assets/missing/missing_models_bypassed.json
Normal file
42
browser_tests/assets/missing/missing_models_bypassed.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"last_node_id": 1,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [256, 256],
|
||||
"size": [315, 98],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 4,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{ "name": "MODEL", "type": "MODEL", "links": null },
|
||||
{ "name": "CLIP", "type": "CLIP", "links": null },
|
||||
{ "name": "VAE", "type": "VAE", "links": null }
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": ["fake_model.safetensors"]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [0, 0]
|
||||
}
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"name": "fake_model.safetensors",
|
||||
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
|
||||
"directory": "checkpoints"
|
||||
}
|
||||
],
|
||||
"version": 0.4
|
||||
}
|
||||
141
browser_tests/assets/missing/missing_models_in_subgraph.json
Normal file
141
browser_tests/assets/missing/missing_models_in_subgraph.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"id": "test-missing-models-in-subgraph",
|
||||
"revision": 0,
|
||||
"last_node_id": 2,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KSampler",
|
||||
"pos": [100, 100],
|
||||
"size": [270, 262],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "model", "type": "MODEL", "link": null },
|
||||
{ "name": "positive", "type": "CONDITIONING", "link": null },
|
||||
{ "name": "negative", "type": "CONDITIONING", "link": null },
|
||||
{ "name": "latent_image", "type": "LATENT", "link": null }
|
||||
],
|
||||
"outputs": [{ "name": "LATENT", "type": "LATENT", "links": [] }],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [0, "randomize", 20, 8, "euler", "simple", 1]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "subgraph-with-missing-model",
|
||||
"pos": [450, 100],
|
||||
"size": [400, 200],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [{ "name": "model", "type": "MODEL", "link": null }],
|
||||
"outputs": [{ "name": "MODEL", "type": "MODEL", "links": null }],
|
||||
"properties": {},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"definitions": {
|
||||
"subgraphs": [
|
||||
{
|
||||
"id": "subgraph-with-missing-model",
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 0,
|
||||
"lastNodeId": 1,
|
||||
"lastLinkId": 2,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
"config": {},
|
||||
"name": "Subgraph with Missing Model",
|
||||
"inputNode": {
|
||||
"id": -10,
|
||||
"bounding": [100, 200, 120, 60]
|
||||
},
|
||||
"outputNode": {
|
||||
"id": -20,
|
||||
"bounding": [500, 200, 120, 60]
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"id": "input1-id",
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"linkIds": [1],
|
||||
"pos": { "0": 150, "1": 220 }
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "output1-id",
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"linkIds": [2],
|
||||
"pos": { "0": 520, "1": 220 }
|
||||
}
|
||||
],
|
||||
"widgets": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [250, 180],
|
||||
"size": [315, 98],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{ "name": "MODEL", "type": "MODEL", "links": [2] },
|
||||
{ "name": "CLIP", "type": "CLIP", "links": null },
|
||||
{ "name": "VAE", "type": "VAE", "links": null }
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": ["fake_model.safetensors"]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": 1,
|
||||
"origin_id": -10,
|
||||
"origin_slot": 0,
|
||||
"target_id": 1,
|
||||
"target_slot": 0,
|
||||
"type": "MODEL"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"origin_id": 1,
|
||||
"origin_slot": 0,
|
||||
"target_id": -20,
|
||||
"target_slot": 0,
|
||||
"type": "MODEL"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [0, 0]
|
||||
}
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"name": "fake_model.safetensors",
|
||||
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
|
||||
"directory": "checkpoints"
|
||||
}
|
||||
],
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -78,7 +78,7 @@
|
||||
{
|
||||
"name": "fake_model.safetensors",
|
||||
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
|
||||
"directory": "text_encoders"
|
||||
"directory": "checkpoints"
|
||||
}
|
||||
],
|
||||
"version": 0.4
|
||||
|
||||
@@ -414,6 +414,8 @@ export const comfyPageFixture = base.extend<{
|
||||
const userId = await comfyPage.setupUser(username)
|
||||
comfyPage.userIds[parallelIndex] = userId
|
||||
|
||||
const isVueNodes = testInfo.tags.includes('@vue-nodes')
|
||||
|
||||
try {
|
||||
await comfyPage.setupSettings({
|
||||
'Comfy.UseNewMenu': 'Top',
|
||||
@@ -436,7 +438,8 @@ export const comfyPageFixture = base.extend<{
|
||||
'Comfy.VersionCompatibility.DisableWarnings': true,
|
||||
// Disable errors tab to prevent missing model detection from
|
||||
// rendering error indicators on nodes during unrelated tests.
|
||||
'Comfy.RightSidePanel.ShowErrorsTab': false
|
||||
'Comfy.RightSidePanel.ShowErrorsTab': false,
|
||||
...(isVueNodes && { 'Comfy.VueNodes.Enabled': true })
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -448,6 +451,10 @@ export const comfyPageFixture = base.extend<{
|
||||
|
||||
await comfyPage.setup()
|
||||
|
||||
if (isVueNodes) {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
}
|
||||
|
||||
const needsPerf =
|
||||
testInfo.tags.includes('@perf') || testInfo.tags.includes('@audit')
|
||||
if (needsPerf) await comfyPage.perf.init()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
|
||||
import { TestIds } from '../selectors'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
|
||||
const ids = TestIds.outputHistory
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { WebSocketRoute } from '@playwright/test'
|
||||
|
||||
import type { RawJobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import type { ComfyPage } from '../ComfyPage'
|
||||
import { createMockJob } from './AssetsHelper'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { createMockJob } from '@e2e/fixtures/helpers/AssetsHelper'
|
||||
|
||||
/**
|
||||
* Helper for simulating prompt execution in e2e tests.
|
||||
|
||||
@@ -83,7 +83,8 @@ export const TestIds = {
|
||||
bookmarksSection: 'node-library-bookmarks-section'
|
||||
},
|
||||
propertiesPanel: {
|
||||
root: 'properties-panel'
|
||||
root: 'properties-panel',
|
||||
errorsTab: 'panel-tab-errors'
|
||||
},
|
||||
subgraphEditor: {
|
||||
toggle: 'subgraph-editor-toggle',
|
||||
|
||||
@@ -8,10 +8,6 @@ import type { WorkspaceStore } from '@e2e/types/globals'
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
test.describe('Actionbar', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
/**
|
||||
* This test ensures that the autoqueue change mode can only queue one change at a time
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import {
|
||||
comfyPageFixture as test,
|
||||
comfyExpect as expect
|
||||
} from '../fixtures/ComfyPage'
|
||||
import { setupBuilder } from '../helpers/builderTestUtils'
|
||||
import { fitToViewInstant } from '../helpers/fitToView'
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import { setupBuilder } from '@e2e/helpers/builderTestUtils'
|
||||
import { fitToViewInstant } from '@e2e/helpers/fitToView'
|
||||
|
||||
const RESIZE_NODE_TITLE = 'Resize Image/Mask'
|
||||
const RESIZE_NODE_ID = '1'
|
||||
|
||||
@@ -4,10 +4,6 @@ import {
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Bottom Panel Logs', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('should open bottom panel via toggle button', async ({ comfyPage }) => {
|
||||
const { bottomPanel } = comfyPage
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@ import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Bottom Panel Shortcuts', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('should toggle shortcuts panel visibility', async ({ comfyPage }) => {
|
||||
const { bottomPanel } = comfyPage
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
/**
|
||||
* Cloud distribution E2E tests.
|
||||
|
||||
@@ -8,10 +8,9 @@ const NODE_TITLE = 'KSampler'
|
||||
|
||||
test.describe(
|
||||
'Collapsed node link positions',
|
||||
{ tag: ['@canvas', '@node'] },
|
||||
{ tag: ['@canvas', '@node', '@vue-nodes'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.workflow.loadWorkflow('default')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
@@ -20,10 +20,6 @@ async function verifyCustomIconSvg(iconElement: Locator) {
|
||||
}
|
||||
|
||||
test.describe('Custom Icons', { tag: '@settings' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('sidebar tab icons use custom SVGs', async ({ comfyPage }) => {
|
||||
// Find the icon in the sidebar
|
||||
const icon = comfyPage.page.locator(
|
||||
|
||||
@@ -20,7 +20,6 @@ async function pressKeyAndExpectRequest(
|
||||
test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
|
||||
test.describe('Sidebar Toggle Shortcuts', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.canvas.click({ position: { x: 400, y: 400 } })
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
@@ -164,8 +163,6 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
|
||||
|
||||
test.describe('Mode and Panel Toggles', () => {
|
||||
test("'Alt+m' toggles app mode", async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
// Set up linearData so app mode has something to show
|
||||
await comfyPage.appMode.enterAppModeWithInputs([])
|
||||
await expect(comfyPage.appMode.linearWidgets).toBeVisible()
|
||||
@@ -180,7 +177,6 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
|
||||
})
|
||||
|
||||
test("'Alt+Shift+m' toggles minimap", async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.Minimap.Visible', true)
|
||||
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
await comfyPage.workflow.loadWorkflow('default')
|
||||
@@ -196,8 +192,6 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
|
||||
})
|
||||
|
||||
test("'Ctrl+`' toggles terminal/logs panel", async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
await expect(comfyPage.bottomPanel.root).toBeHidden()
|
||||
|
||||
await comfyPage.page.keyboard.press('Control+Backquote')
|
||||
|
||||
@@ -103,7 +103,6 @@ test.describe('Support', () => {
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
// Prevent loading the external page
|
||||
await comfyPage.page
|
||||
.context()
|
||||
|
||||
@@ -7,7 +7,6 @@ test.describe('Sign In dialog', { tag: '@ui' }, () => {
|
||||
let dialog: SignInDialog
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
dialog = new SignInDialog(comfyPage.page)
|
||||
await dialog.open()
|
||||
})
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
comfyExpect as expect
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
import { cleanupFakeModel } from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
|
||||
test.describe('Error overlay', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.RightSidePanel.ShowErrorsTab',
|
||||
true
|
||||
@@ -47,16 +47,7 @@ test.describe('Error overlay', { tag: '@ui' }, () => {
|
||||
test('Should display "Show missing models" button for missing model errors', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await expect
|
||||
.poll(() =>
|
||||
comfyPage.page.evaluate(async (url: string) => {
|
||||
const response = await fetch(
|
||||
`${url}/api/devtools/cleanup_fake_model`
|
||||
)
|
||||
return response.ok
|
||||
}, comfyPage.url)
|
||||
)
|
||||
.toBeTruthy()
|
||||
await cleanupFakeModel(comfyPage)
|
||||
|
||||
await comfyPage.workflow.loadWorkflow('missing/missing_models')
|
||||
|
||||
@@ -117,6 +108,33 @@ test.describe('Error overlay', { tag: '@ui' }, () => {
|
||||
await comfyPage.keyboard.redo()
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
})
|
||||
|
||||
test('Does not resurface error overlay when switching back to workflow with missing nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Sidebar'
|
||||
)
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
|
||||
await comfyPage.workflow.loadWorkflow('missing/missing_nodes')
|
||||
|
||||
const errorOverlay = getOverlay(comfyPage.page)
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
|
||||
await errorOverlay
|
||||
.getByTestId(TestIds.dialogs.errorOverlayDismiss)
|
||||
.click()
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
|
||||
|
||||
await comfyPage.menu.workflowsTab.switchToWorkflow('missing_nodes')
|
||||
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('See Errors flow', () => {
|
||||
|
||||
@@ -13,10 +13,6 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
type TestSettingId = keyof Settings
|
||||
|
||||
test.describe('Topbar commands', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('Should allow registering topbar commands', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window.app!.registerExtension({
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
|
||||
test.describe('Focus Mode', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setup()
|
||||
})
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ import { expect } from '@playwright/test'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Image Compare', { tag: '@widget' }, () => {
|
||||
test.describe('Image Compare', { tag: ['@widget', '@vue-nodes'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.workflow.loadWorkflow('widgets/image_compare_widget')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
@@ -895,7 +895,6 @@ test.describe('Load workflow', { tag: '@screenshot' }, () => {
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
workflowA = generateUniqueFilename()
|
||||
await comfyPage.menu.topbar.saveWorkflow(workflowA)
|
||||
workflowB = generateUniqueFilename()
|
||||
@@ -970,7 +969,6 @@ test.describe('Load workflow', { tag: '@screenshot' }, () => {
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
workflowA = generateUniqueFilename()
|
||||
await comfyPage.menu.topbar.saveWorkflow(workflowA)
|
||||
workflowB = generateUniqueFilename()
|
||||
@@ -1060,13 +1058,10 @@ test.describe('Load workflow', { tag: '@screenshot' }, () => {
|
||||
})
|
||||
|
||||
test.describe('Load duplicate workflow', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('A workflow can be loaded multiple times in a row', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.workflow.loadWorkflow('nodes/single_ksampler')
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
|
||||
test.describe('Job History Actions', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setup()
|
||||
|
||||
// Expand the queue overlay so the JobHistoryActionsMenu is visible
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
|
||||
test.describe('Linear Mode', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setup()
|
||||
})
|
||||
|
||||
|
||||
@@ -3,11 +3,7 @@ import { expect } from '@playwright/test'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Mask Editor', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
})
|
||||
|
||||
test.describe('Mask Editor', { tag: '@vue-nodes' }, () => {
|
||||
async function loadImageOnNode(comfyPage: ComfyPage) {
|
||||
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
@@ -3,10 +3,6 @@ import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Menu', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('Can register sidebar tab', async ({ comfyPage }) => {
|
||||
const initialChildrenCount = await comfyPage.menu.buttons.count()
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ async function clickMinimapAt(
|
||||
|
||||
test.describe('Minimap', { tag: '@canvas' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.Minimap.Visible', true)
|
||||
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
await comfyPage.workflow.loadWorkflow('default')
|
||||
|
||||
@@ -88,7 +88,6 @@ async function setLocaleAndWaitForWorkflowReload(
|
||||
test.describe('Node Help', { tag: ['@slow', '@ui'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
|
||||
})
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
|
||||
test.describe('Node Library Essentials Tab', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
// Enable the essentials feature flag via the reactive serverFeatureFlags ref.
|
||||
// In production, this flag comes via WebSocket or remoteConfig (cloud only).
|
||||
// The localhost test server has neither, so we set it directly.
|
||||
|
||||
@@ -2,10 +2,13 @@ import type { WebSocketRoute } from '@playwright/test'
|
||||
import { mergeTests } from '@playwright/test'
|
||||
|
||||
import type { RawJobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { comfyPageFixture, comfyExpect as expect } from '../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
import { webSocketFixture } from '../fixtures/ws'
|
||||
import { ExecutionHelper } from '../fixtures/helpers/ExecutionHelper'
|
||||
import {
|
||||
comfyPageFixture,
|
||||
comfyExpect as expect
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { webSocketFixture } from '@e2e/fixtures/ws'
|
||||
import { ExecutionHelper } from '@e2e/fixtures/helpers/ExecutionHelper'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
@@ -40,7 +43,6 @@ function imageOutput(...filenames: string[]) {
|
||||
|
||||
test.describe('Output History', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.appMode.enterAppModeWithInputs([[KSAMPLER_NODE, 'seed']])
|
||||
await expect(comfyPage.appMode.linearWidgets).toBeVisible()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
@@ -9,10 +9,9 @@ import {
|
||||
triggerSerialization
|
||||
} from '@e2e/helpers/painter'
|
||||
|
||||
test.describe('Painter', { tag: '@widget' }, () => {
|
||||
test.describe('Painter', { tag: ['@widget', '@vue-nodes'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => window.app?.graph?.clear())
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.workflow.loadWorkflow('widgets/painter_widget')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
@@ -2,53 +2,53 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Paste Image context menu option', { tag: ['@node'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
})
|
||||
test.describe(
|
||||
'Paste Image context menu option',
|
||||
{ tag: ['@node', '@vue-nodes'] },
|
||||
() => {
|
||||
test('shows Paste Image in LoadImage node context menu', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
|
||||
|
||||
test('shows Paste Image in LoadImage node context menu', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
|
||||
const loadImageNode = (
|
||||
await comfyPage.nodeOps.getNodeRefsByType('LoadImage')
|
||||
)[0]
|
||||
|
||||
const loadImageNode = (
|
||||
await comfyPage.nodeOps.getNodeRefsByType('LoadImage')
|
||||
)[0]
|
||||
const nodeEl = comfyPage.page.locator(
|
||||
`[data-node-id="${loadImageNode.id}"]`
|
||||
)
|
||||
await nodeEl.click({ button: 'right' })
|
||||
const menu = comfyPage.page.locator('.p-contextmenu')
|
||||
await menu.waitFor({ state: 'visible' })
|
||||
const menuLabels = await menu
|
||||
.locator('[role="menuitem"] span.flex-1')
|
||||
.allInnerTexts()
|
||||
|
||||
const nodeEl = comfyPage.page.locator(
|
||||
`[data-node-id="${loadImageNode.id}"]`
|
||||
)
|
||||
await nodeEl.click({ button: 'right' })
|
||||
const menu = comfyPage.page.locator('.p-contextmenu')
|
||||
await menu.waitFor({ state: 'visible' })
|
||||
const menuLabels = await menu
|
||||
.locator('[role="menuitem"] span.flex-1')
|
||||
.allInnerTexts()
|
||||
expect(menuLabels).toContain('Paste Image')
|
||||
})
|
||||
|
||||
expect(menuLabels).toContain('Paste Image')
|
||||
})
|
||||
test('does not show Paste Image on output-only image nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('nodes/single_save_image_node')
|
||||
|
||||
test('does not show Paste Image on output-only image nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('nodes/single_save_image_node')
|
||||
const saveImageNode = (
|
||||
await comfyPage.nodeOps.getNodeRefsByType('SaveImage')
|
||||
)[0]
|
||||
|
||||
const saveImageNode = (
|
||||
await comfyPage.nodeOps.getNodeRefsByType('SaveImage')
|
||||
)[0]
|
||||
const nodeEl = comfyPage.page.locator(
|
||||
`[data-node-id="${saveImageNode.id}"]`
|
||||
)
|
||||
await nodeEl.click({ button: 'right' })
|
||||
const menu = comfyPage.page.locator('.p-contextmenu')
|
||||
await menu.waitFor({ state: 'visible' })
|
||||
const menuLabels = await menu
|
||||
.locator('[role="menuitem"] span.flex-1')
|
||||
.allInnerTexts()
|
||||
|
||||
const nodeEl = comfyPage.page.locator(
|
||||
`[data-node-id="${saveImageNode.id}"]`
|
||||
)
|
||||
await nodeEl.click({ button: 'right' })
|
||||
const menu = comfyPage.page.locator('.p-contextmenu')
|
||||
await menu.waitFor({ state: 'visible' })
|
||||
const menuLabels = await menu
|
||||
.locator('[role="menuitem"] span.flex-1')
|
||||
.allInnerTexts()
|
||||
|
||||
expect(menuLabels).not.toContain('Paste Image')
|
||||
expect(menuLabels).not.toContain('Open Image')
|
||||
})
|
||||
})
|
||||
expect(menuLabels).not.toContain('Paste Image')
|
||||
expect(menuLabels).not.toContain('Open Image')
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2,8 +2,9 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
import { PropertiesPanelHelper } from '@e2e/tests/propertiesPanel/PropertiesPanelHelper'
|
||||
|
||||
export async function openErrorsTabViaSeeErrors(
|
||||
export async function loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage: ComfyPage,
|
||||
workflow: string
|
||||
) {
|
||||
@@ -15,3 +16,30 @@ export async function openErrorsTabViaSeeErrors(
|
||||
await errorOverlay.getByTestId(TestIds.dialogs.errorOverlaySeeErrors).click()
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
}
|
||||
|
||||
export async function openErrorsTab(comfyPage: ComfyPage) {
|
||||
const panel = new PropertiesPanelHelper(comfyPage.page)
|
||||
await panel.open(comfyPage.actionbar.propertiesButton)
|
||||
|
||||
const errorsTab = comfyPage.page.getByTestId(
|
||||
TestIds.propertiesPanel.errorsTab
|
||||
)
|
||||
await expect(errorsTab).toBeVisible()
|
||||
await errorsTab.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the fake model file from the backend so it is detected as missing.
|
||||
* Fixture URLs (e.g. http://localhost:8188/...) are not actually downloaded
|
||||
* during tests — they only serve as metadata for the missing model UI.
|
||||
*/
|
||||
export async function cleanupFakeModel(comfyPage: ComfyPage) {
|
||||
await expect
|
||||
.poll(() =>
|
||||
comfyPage.page.evaluate(async (url: string) => {
|
||||
const response = await fetch(`${url}/api/devtools/cleanup_fake_model`)
|
||||
return response.ok
|
||||
}, comfyPage.url)
|
||||
)
|
||||
.toBeTruthy()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { PropertiesPanelHelper } from '@e2e/tests/propertiesPanel/PropertiesPane
|
||||
|
||||
test.describe('Errors tab - common', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.RightSidePanel.ShowErrorsTab',
|
||||
true
|
||||
|
||||
@@ -7,7 +7,6 @@ import { TestIds } from '@e2e/fixtures/selectors'
|
||||
|
||||
test.describe('Errors tab - Execution errors', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.RightSidePanel.ShowErrorsTab',
|
||||
true
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect } from '@playwright/test'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
import { openErrorsTabViaSeeErrors } from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
import { loadWorkflowAndOpenErrorsTab } from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
|
||||
async function uploadFileViaDropzone(comfyPage: ComfyPage) {
|
||||
const dropzone = comfyPage.page.getByTestId(
|
||||
@@ -38,7 +38,6 @@ function getDropzone(comfyPage: ComfyPage) {
|
||||
|
||||
test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.RightSidePanel.ShowErrorsTab',
|
||||
true
|
||||
@@ -47,7 +46,10 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
|
||||
test.describe('Detection', () => {
|
||||
test('Shows missing media group in errors tab', async ({ comfyPage }) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_media_single')
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.dialogs.missingMediaGroup)
|
||||
@@ -57,7 +59,7 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test('Shows correct number of missing media rows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_multiple'
|
||||
)
|
||||
@@ -68,7 +70,10 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test('Shows upload dropzone and library select for each missing item', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_media_single')
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
|
||||
await expect(getDropzone(comfyPage)).toBeVisible()
|
||||
await expect(
|
||||
@@ -81,7 +86,10 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test('Upload via file picker shows status card then allows confirm', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_media_single')
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
await uploadFileViaDropzone(comfyPage)
|
||||
|
||||
await expect(getStatusCard(comfyPage)).toBeVisible()
|
||||
@@ -95,7 +103,10 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test('Selecting from library shows status card then allows confirm', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_media_single')
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
|
||||
const librarySelect = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingMediaLibrarySelect
|
||||
@@ -122,7 +133,10 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test('Cancelling pending selection returns to upload/library UI', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_media_single')
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
await uploadFileViaDropzone(comfyPage)
|
||||
|
||||
await expect(getStatusCard(comfyPage)).toBeVisible()
|
||||
@@ -141,7 +155,10 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test('Missing Inputs group disappears when all items are resolved', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_media_single')
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
await uploadFileViaDropzone(comfyPage)
|
||||
await confirmPendingSelection(comfyPage)
|
||||
|
||||
@@ -155,7 +172,10 @@ test.describe('Errors tab - Missing media', { tag: '@ui' }, () => {
|
||||
test('Locate button navigates canvas to the missing media node', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_media_single')
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
|
||||
const offsetBefore = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app']?.canvas
|
||||
|
||||
@@ -6,29 +6,24 @@ import {
|
||||
interceptClipboardWrite,
|
||||
getClipboardText
|
||||
} from '@e2e/helpers/clipboardSpy'
|
||||
import { openErrorsTabViaSeeErrors } from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
import {
|
||||
cleanupFakeModel,
|
||||
loadWorkflowAndOpenErrorsTab
|
||||
} from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
|
||||
test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.RightSidePanel.ShowErrorsTab',
|
||||
true
|
||||
)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await comfyPage.page.evaluate(async (url: string) => {
|
||||
const response = await fetch(`${url}/api/devtools/cleanup_fake_model`)
|
||||
return response.ok
|
||||
}, comfyPage.url)
|
||||
})
|
||||
.toBeTruthy()
|
||||
await cleanupFakeModel(comfyPage)
|
||||
})
|
||||
|
||||
test('Should show missing models group in errors tab', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_models')
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.dialogs.missingModelsGroup)
|
||||
@@ -38,7 +33,7 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
|
||||
test('Should display model name with referencing node count', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_models')
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const modelsGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
@@ -49,7 +44,7 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
|
||||
test('Should expand model row to show referencing nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_models_with_nodes'
|
||||
)
|
||||
@@ -69,14 +64,14 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
|
||||
})
|
||||
|
||||
test('Should copy model name to clipboard', async ({ comfyPage }) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_models')
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
await interceptClipboardWrite(comfyPage.page)
|
||||
|
||||
const copyButton = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelCopyName
|
||||
)
|
||||
await expect(copyButton.first()).toBeVisible()
|
||||
await copyButton.first().click()
|
||||
await copyButton.first().dispatchEvent('click')
|
||||
|
||||
const copiedText = await getClipboardText(comfyPage.page)
|
||||
expect(copiedText).toContain('fake_model.safetensors')
|
||||
@@ -86,7 +81,7 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
|
||||
test('Should show Copy URL button for non-asset models', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_models')
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const copyUrlButton = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelCopyUrl
|
||||
@@ -97,7 +92,7 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
|
||||
test('Should show Download button for downloadable models', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_models')
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const downloadButton = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelDownload
|
||||
|
||||
@@ -2,11 +2,10 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
import { openErrorsTabViaSeeErrors } from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
import { loadWorkflowAndOpenErrorsTab } from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
|
||||
test.describe('Errors tab - Missing nodes', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.RightSidePanel.ShowErrorsTab',
|
||||
true
|
||||
@@ -14,7 +13,7 @@ test.describe('Errors tab - Missing nodes', { tag: '@ui' }, () => {
|
||||
})
|
||||
|
||||
test('Should show MissingNodeCard in errors tab', async ({ comfyPage }) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_nodes')
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_nodes')
|
||||
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.dialogs.missingNodeCard)
|
||||
@@ -22,7 +21,7 @@ test.describe('Errors tab - Missing nodes', { tag: '@ui' }, () => {
|
||||
})
|
||||
|
||||
test('Should show missing node packs group', async ({ comfyPage }) => {
|
||||
await openErrorsTabViaSeeErrors(comfyPage, 'missing/missing_nodes')
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_nodes')
|
||||
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.dialogs.missingNodePacksGroup)
|
||||
@@ -32,7 +31,7 @@ test.describe('Errors tab - Missing nodes', { tag: '@ui' }, () => {
|
||||
test('Should expand pack group to reveal node type names', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_nodes_in_subgraph'
|
||||
)
|
||||
@@ -52,7 +51,7 @@ test.describe('Errors tab - Missing nodes', { tag: '@ui' }, () => {
|
||||
})
|
||||
|
||||
test('Should collapse expanded pack group', async ({ comfyPage }) => {
|
||||
await openErrorsTabViaSeeErrors(
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_nodes_in_subgraph'
|
||||
)
|
||||
@@ -80,7 +79,7 @@ test.describe('Errors tab - Missing nodes', { tag: '@ui' }, () => {
|
||||
test('Locate node button is visible for expanded pack nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openErrorsTabViaSeeErrors(
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_nodes_in_subgraph'
|
||||
)
|
||||
|
||||
519
browser_tests/tests/propertiesPanel/errorsTabModeAware.spec.ts
Normal file
519
browser_tests/tests/propertiesPanel/errorsTabModeAware.spec.ts
Normal file
@@ -0,0 +1,519 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
import {
|
||||
cleanupFakeModel,
|
||||
openErrorsTab,
|
||||
loadWorkflowAndOpenErrorsTab
|
||||
} from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
|
||||
test.describe('Errors tab - Mode-aware errors', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.RightSidePanel.ShowErrorsTab',
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Missing nodes', () => {
|
||||
test('Deleting a missing node removes its error from the errors tab', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_nodes')
|
||||
|
||||
const missingNodeGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingNodePacksGroup
|
||||
)
|
||||
await expect(missingNodeGroup).toBeVisible()
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node.delete()
|
||||
|
||||
await expect(missingNodeGroup).toBeHidden()
|
||||
})
|
||||
|
||||
test('Undo after bypass restores error without showing overlay', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_nodes')
|
||||
|
||||
const missingNodeGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingNodePacksGroup
|
||||
)
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(missingNodeGroup).toBeVisible()
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingNodeGroup).toBeHidden()
|
||||
|
||||
await comfyPage.keyboard.undo()
|
||||
await expect.poll(() => node.isBypassed()).toBeFalsy()
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingNodeGroup).toBeVisible()
|
||||
|
||||
await comfyPage.keyboard.redo()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingNodeGroup).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Missing models', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await cleanupFakeModel(comfyPage)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ comfyPage }) => {
|
||||
await cleanupFakeModel(comfyPage)
|
||||
})
|
||||
|
||||
test('Loading a workflow with all nodes bypassed shows no errors', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('missing/missing_models_bypassed')
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
|
||||
await comfyPage.actionbar.propertiesButton.click()
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.propertiesPanel.errorsTab)
|
||||
).toBeHidden()
|
||||
})
|
||||
|
||||
test('Bypassing a node hides its error, un-bypassing restores it', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingModelGroup).toBeHidden()
|
||||
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeFalsy()
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
})
|
||||
|
||||
test('Pasting a node with missing model increases referencing node count', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
await expect(missingModelGroup).toContainText(
|
||||
/fake_model\.safetensors\s*\(1\)/
|
||||
)
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node.click('title')
|
||||
await comfyPage.clipboard.copy()
|
||||
await comfyPage.clipboard.paste()
|
||||
|
||||
await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toBe(2)
|
||||
|
||||
await comfyPage.canvas.click()
|
||||
await expect(missingModelGroup).toContainText(
|
||||
/fake_model\.safetensors\s*\(2\)/
|
||||
)
|
||||
})
|
||||
|
||||
test('Pasting a bypassed node does not add a new error', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingModelGroup).toBeHidden()
|
||||
|
||||
await comfyPage.clipboard.copy()
|
||||
await comfyPage.clipboard.paste()
|
||||
|
||||
await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toBe(2)
|
||||
await expect(missingModelGroup).toBeHidden()
|
||||
})
|
||||
|
||||
test('Deleting a node with missing model removes its error', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node.delete()
|
||||
|
||||
await expect(missingModelGroup).toBeHidden()
|
||||
})
|
||||
|
||||
test('Undo after bypass restores error without showing overlay', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingModelGroup).toBeHidden()
|
||||
|
||||
await comfyPage.keyboard.undo()
|
||||
await expect.poll(() => node.isBypassed()).toBeFalsy()
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
|
||||
await comfyPage.keyboard.redo()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingModelGroup).toBeHidden()
|
||||
})
|
||||
|
||||
test('Selecting a node filters errors tab to only that node', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_models_with_nodes'
|
||||
)
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
await expect(missingModelGroup).toContainText(/\(2\)/)
|
||||
|
||||
const node1 = await comfyPage.nodeOps.getNodeRefById('1')
|
||||
await node1.click('title')
|
||||
await expect(missingModelGroup).toContainText(/\(1\)/)
|
||||
|
||||
await comfyPage.canvas.click()
|
||||
await expect(missingModelGroup).toContainText(/\(2\)/)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Missing media', () => {
|
||||
test('Loading a workflow with all nodes bypassed shows no errors', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('missing/missing_media_bypassed')
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
|
||||
await comfyPage.actionbar.propertiesButton.click()
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.propertiesPanel.errorsTab)
|
||||
).toBeHidden()
|
||||
})
|
||||
|
||||
test('Bypassing a node hides its error, un-bypassing restores it', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
|
||||
const missingMediaGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingMediaGroup
|
||||
)
|
||||
await expect(missingMediaGroup).toBeVisible()
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('10')
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingMediaGroup).toBeHidden()
|
||||
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeFalsy()
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingMediaGroup).toBeVisible()
|
||||
})
|
||||
|
||||
test('Pasting a bypassed node does not add a new error', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await loadWorkflowAndOpenErrorsTab(
|
||||
comfyPage,
|
||||
'missing/missing_media_single'
|
||||
)
|
||||
|
||||
const missingMediaGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingMediaGroup
|
||||
)
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('10')
|
||||
await node.click('title')
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => node.isBypassed()).toBeTruthy()
|
||||
await expect(missingMediaGroup).toBeHidden()
|
||||
|
||||
await comfyPage.clipboard.copy()
|
||||
await comfyPage.clipboard.paste()
|
||||
|
||||
await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toBe(2)
|
||||
await expect(missingMediaGroup).toBeHidden()
|
||||
})
|
||||
|
||||
test('Selecting a node filters errors tab to only that node', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('missing/missing_media_multiple')
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
await errorOverlay
|
||||
.getByTestId(TestIds.dialogs.errorOverlayDismiss)
|
||||
.click()
|
||||
|
||||
const mediaRows = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingMediaRow
|
||||
)
|
||||
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(mediaRows).toHaveCount(2)
|
||||
|
||||
const node = await comfyPage.nodeOps.getNodeRefById('10')
|
||||
await node.click('title')
|
||||
await expect(mediaRows).toHaveCount(1)
|
||||
|
||||
await comfyPage.canvas.click({ position: { x: 400, y: 600 } })
|
||||
await expect(mediaRows).toHaveCount(2)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Subgraph', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await cleanupFakeModel(comfyPage)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ comfyPage }) => {
|
||||
await cleanupFakeModel(comfyPage)
|
||||
})
|
||||
|
||||
test('Bypassing a subgraph hides interior errors, un-bypassing restores them', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow(
|
||||
'missing/missing_models_in_subgraph'
|
||||
)
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
await errorOverlay
|
||||
.getByTestId(TestIds.dialogs.errorOverlayDismiss)
|
||||
.click()
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
|
||||
const subgraphNode = await comfyPage.nodeOps.getNodeRefById('2')
|
||||
const errorsTab = comfyPage.page.getByTestId(
|
||||
TestIds.propertiesPanel.errorsTab
|
||||
)
|
||||
|
||||
await comfyPage.keyboard.selectAll()
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => subgraphNode.isBypassed()).toBeTruthy()
|
||||
|
||||
await comfyPage.actionbar.propertiesButton.click()
|
||||
await expect(errorsTab).toBeHidden()
|
||||
|
||||
await comfyPage.keyboard.selectAll()
|
||||
await comfyPage.keyboard.bypass()
|
||||
await expect.poll(() => subgraphNode.isBypassed()).toBeFalsy()
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
})
|
||||
|
||||
test('Deleting a node inside a subgraph removes its missing model error', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Regression: before the execId fix, onNodeRemoved fell back to the
|
||||
// interior node's local id (e.g. "1") when node.graph was already
|
||||
// null, so the error keyed under "2:1" was never removed.
|
||||
await comfyPage.workflow.loadWorkflow(
|
||||
'missing/missing_models_in_subgraph'
|
||||
)
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
await errorOverlay
|
||||
.getByTestId(TestIds.dialogs.errorOverlayDismiss)
|
||||
.click()
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
|
||||
const subgraphNode = await comfyPage.nodeOps.getNodeRefById('2')
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
// Select-all + Delete: interior node IDs may be reassigned during
|
||||
// subgraph configure when they collide with root-graph IDs, so
|
||||
// looking up by static id can fail.
|
||||
await comfyPage.keyboard.selectAll()
|
||||
await comfyPage.page.keyboard.press('Delete')
|
||||
|
||||
await expect(missingModelGroup).toBeHidden()
|
||||
})
|
||||
|
||||
test('Deleting a node inside a subgraph removes its missing node-type error', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('missing/missing_nodes_in_subgraph')
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
await errorOverlay
|
||||
.getByTestId(TestIds.dialogs.errorOverlayDismiss)
|
||||
.click()
|
||||
|
||||
const missingNodeGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingNodePacksGroup
|
||||
)
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingNodeGroup).toBeVisible()
|
||||
|
||||
const subgraphNode = await comfyPage.nodeOps.getNodeRefById('2')
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
// Select-all + Delete: interior node IDs may be reassigned during
|
||||
// subgraph configure when they collide with root-graph IDs, so
|
||||
// looking up by static id can fail.
|
||||
await comfyPage.keyboard.selectAll()
|
||||
await comfyPage.page.keyboard.press('Delete')
|
||||
|
||||
await expect(missingNodeGroup).toBeHidden()
|
||||
})
|
||||
|
||||
test('Bypassing a node inside a subgraph hides its error, un-bypassing restores it', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow(
|
||||
'missing/missing_models_in_subgraph'
|
||||
)
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
await errorOverlay
|
||||
.getByTestId(TestIds.dialogs.errorOverlayDismiss)
|
||||
.click()
|
||||
|
||||
const missingModelGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingModelsGroup
|
||||
)
|
||||
|
||||
const subgraphNode = await comfyPage.nodeOps.getNodeRefById('2')
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
await comfyPage.keyboard.selectAll()
|
||||
await comfyPage.keyboard.bypass()
|
||||
|
||||
const errorsTab = comfyPage.page.getByTestId(
|
||||
TestIds.propertiesPanel.errorsTab
|
||||
)
|
||||
await comfyPage.actionbar.propertiesButton.click()
|
||||
await expect(errorsTab).toBeHidden()
|
||||
|
||||
await comfyPage.keyboard.selectAll()
|
||||
await comfyPage.keyboard.bypass()
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingModelGroup).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Workflow switching', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Sidebar'
|
||||
)
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
})
|
||||
|
||||
test('Restores missing nodes in errors tab when switching back to workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.workflow.loadWorkflow('missing/missing_nodes')
|
||||
|
||||
const errorOverlay = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.errorOverlay
|
||||
)
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
await errorOverlay
|
||||
.getByTestId(TestIds.dialogs.errorOverlayDismiss)
|
||||
.click()
|
||||
|
||||
const missingNodeGroup = comfyPage.page.getByTestId(
|
||||
TestIds.dialogs.missingNodePacksGroup
|
||||
)
|
||||
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingNodeGroup).toBeVisible()
|
||||
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
await comfyPage.command.executeCommand('Comfy.NewBlankWorkflow')
|
||||
await expect(missingNodeGroup).toBeHidden()
|
||||
|
||||
await comfyPage.menu.workflowsTab.switchToWorkflow('missing_nodes')
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(missingNodeGroup).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,13 +3,11 @@ import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
import { PropertiesPanelHelper } from '@e2e/tests/propertiesPanel/PropertiesPanelHelper'
|
||||
|
||||
test.describe('Properties panel - Node settings', () => {
|
||||
test.describe('Properties panel - Node settings', { tag: '@vue-nodes' }, () => {
|
||||
let panel: PropertiesPanelHelper
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
panel = new PropertiesPanelHelper(comfyPage.page)
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
await comfyPage.actionbar.propertiesButton.click()
|
||||
await comfyPage.nodeOps.selectNodes(['KSampler'])
|
||||
await panel.switchToTab('Settings')
|
||||
|
||||
@@ -19,10 +19,6 @@ function createMockRelease(overrides?: Partial<ReleaseNote>): ReleaseNote {
|
||||
}
|
||||
|
||||
test.describe('Release Notifications', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('should show help center with release information', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -47,7 +47,6 @@ test.describe('Remote COMBO Widget', { tag: '@widget' }, () => {
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.NodeSearchBoxImpl',
|
||||
@@ -57,7 +56,6 @@ test.describe('Remote COMBO Widget', { tag: '@widget' }, () => {
|
||||
|
||||
test.describe('Loading options', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route, request) => {
|
||||
|
||||
@@ -3,10 +3,8 @@ import { expect } from '@playwright/test'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('MediaLightbox', { tag: ['@slow'] }, () => {
|
||||
test.describe('MediaLightbox', { tag: ['@slow', '@vue-nodes'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
})
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ import {
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
|
||||
test.describe('Right Side Panel Tabs', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('Properties panel opens with workflow overview', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -4,13 +4,8 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe(
|
||||
'Save Image and WEBM preview',
|
||||
{ tag: ['@screenshot', '@widget'] },
|
||||
{ tag: ['@screenshot', '@widget', '@vue-nodes'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('Can preview both SaveImage and SaveWEBM outputs', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -3,14 +3,7 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('@canvas Selection Rectangle', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('@canvas Selection Rectangle', { tag: '@vue-nodes' }, () => {
|
||||
test('Ctrl+A selects all nodes', async ({ comfyPage }) => {
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
|
||||
@@ -28,10 +28,6 @@ async function selectNodeWithPan(comfyPage: ComfyPage, nodeRef: NodeReference) {
|
||||
await nodeRef.click('title')
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.describe('Selection Toolbox - Button Actions', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
|
||||
@@ -43,7 +43,6 @@ async function renameInlineFolder(comfyPage: ComfyPage, newName: string) {
|
||||
|
||||
test.describe('Node library sidebar', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
|
||||
await comfyPage.settings.setSetting(bookmarksSettingId, [])
|
||||
await comfyPage.settings.setSetting(bookmarksCustomizationSettingId, {})
|
||||
|
||||
@@ -4,7 +4,6 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Node library sidebar V2', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', true)
|
||||
|
||||
const tab = comfyPage.menu.nodeLibraryTabV2
|
||||
|
||||
@@ -5,7 +5,6 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Sidebar splitter width independence', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.Sidebar.UnifiedWidth', true)
|
||||
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
|
||||
})
|
||||
|
||||
@@ -2,10 +2,10 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
import { openErrorsTab } from '@e2e/tests/propertiesPanel/ErrorsTabHelper'
|
||||
|
||||
test.describe('Workflows sidebar', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Sidebar'
|
||||
@@ -249,7 +249,7 @@ test.describe('Workflows sidebar', () => {
|
||||
.toEqual('workflow1')
|
||||
})
|
||||
|
||||
test('Reports missing nodes warning again when switching back to workflow', async ({
|
||||
test('Restores missing nodes errors silently when switching back to workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting(
|
||||
@@ -271,11 +271,17 @@ test.describe('Workflows sidebar', () => {
|
||||
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
|
||||
// Switch back to the missing_nodes workflow — overlay should NOT
|
||||
// reappear (silent restore), but errors tab should have content
|
||||
await comfyPage.menu.workflowsTab.switchToWorkflow('missing_nodes')
|
||||
|
||||
await expect(errorOverlay).toBeVisible()
|
||||
await expect(errorOverlay).toBeHidden()
|
||||
|
||||
// Errors tab should still show missing nodes after silent restore
|
||||
await openErrorsTab(comfyPage)
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.dialogs.missingNodePacksGroup)
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('Can close saved-workflows from the open workflows section', async ({
|
||||
|
||||
@@ -11,14 +11,8 @@ async function openVueNodeContextMenu(comfyPage: ComfyPage, nodeTitle: string) {
|
||||
|
||||
test.describe(
|
||||
'Subgraph Duplicate Independent Values',
|
||||
{ tag: ['@slow', '@subgraph'] },
|
||||
{ tag: ['@slow', '@subgraph', '@vue-nodes'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('Duplicated subgraphs maintain independent widget values', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -8,10 +8,6 @@ const domPreviewSelector = '.image-preview'
|
||||
|
||||
test.describe('Subgraph Lifecycle', { tag: ['@subgraph'] }, () => {
|
||||
test.describe('Cleanup Behavior After Promoted Source Removal', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('Deleting the promoted source removes the exterior DOM widget', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -33,10 +33,6 @@ function hasVisibleNodeInViewport() {
|
||||
|
||||
test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
|
||||
test.describe('Subgraph Navigation and UI', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('Breadcrumb updates when subgraph node title is changed', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
@@ -108,10 +104,6 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
|
||||
})
|
||||
|
||||
test.describe('Navigation Hotkeys', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('Navigation hotkey can be customized', async ({ comfyPage }) => {
|
||||
await comfyPage.workflow.loadWorkflow('subgraphs/basic-subgraph')
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
@@ -182,10 +182,6 @@ test.describe(
|
||||
})
|
||||
|
||||
test.describe('Manual Promote/Demote via Context Menu', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('Can promote and un-promote a widget from inside a subgraph', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
@@ -477,8 +473,6 @@ test.describe(
|
||||
test('Nested promoted widget entries reflect interior changes after slot removal', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
await comfyPage.workflow.loadWorkflow(
|
||||
'subgraphs/subgraph-nested-promotion'
|
||||
)
|
||||
@@ -522,8 +516,6 @@ test.describe(
|
||||
test('Removing I/O slot removes associated promoted widget', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
await comfyPage.workflow.loadWorkflow(
|
||||
'subgraphs/subgraph-with-promoted-text-widget'
|
||||
)
|
||||
|
||||
@@ -115,8 +115,6 @@ test.describe('Subgraph Promotion DOM', { tag: ['@subgraph'] }, () => {
|
||||
test('DOM elements are cleaned up when widget is disconnected from I/O', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
await comfyPage.workflow.loadWorkflow(
|
||||
'subgraphs/subgraph-with-promoted-text-widget'
|
||||
)
|
||||
|
||||
@@ -42,7 +42,6 @@ async function searchAndExpectResult(
|
||||
|
||||
test.describe('Subgraph Search Aliases', { tag: ['@subgraph'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.NodeSearchBoxImpl',
|
||||
'v1 (legacy)'
|
||||
|
||||
@@ -4,15 +4,8 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe(
|
||||
'Zero UUID workflow: subgraph undo rendering',
|
||||
{ tag: ['@workflow', '@subgraph'] },
|
||||
{ tag: ['@workflow', '@subgraph', '@vue-nodes'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
test.setTimeout(30000) // Extend timeout as we need to reload the page an additional time
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.page.reload() // Reload page as we need to enter in Vue mode
|
||||
await comfyPage.page.waitForFunction(() => !!window.app?.graph)
|
||||
})
|
||||
|
||||
test('Undo after subgraph enter/exit renders all nodes when workflow starts with zero UUID', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -46,10 +46,6 @@ test.describe(
|
||||
'Subgraph promoted widget panel',
|
||||
{ tag: ['@node', '@widget'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.describe('SubgraphEditor (Settings panel)', () => {
|
||||
test('linked promoted widgets have hide toggle disabled', async ({
|
||||
comfyPage
|
||||
|
||||
@@ -16,10 +16,6 @@ async function checkTemplateFileExists(
|
||||
}
|
||||
|
||||
test.describe('Templates', { tag: ['@slow', '@workflow'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('should have a JSON workflow file for each template', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
|
||||
test.describe('Toast Notifications', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setup()
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Workflow tabs', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Topbar'
|
||||
|
||||
@@ -37,7 +37,6 @@ test.describe('Version Mismatch Warnings', { tag: '@slow' }, () => {
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.VersionCompatibility.DisableWarnings',
|
||||
false
|
||||
|
||||
@@ -112,11 +112,9 @@ async function getNodeGroupCenteringErrors(
|
||||
})
|
||||
}
|
||||
|
||||
test.describe('Vue Node Groups', { tag: '@screenshot' }, () => {
|
||||
test.describe('Vue Node Groups', { tag: ['@screenshot', '@vue-nodes'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting('Comfy.Minimap.ShowGroups', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should allow creating groups with hotkey', async ({ comfyPage }) => {
|
||||
|
||||
@@ -3,12 +3,7 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Nodes Canvas Pan', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Nodes Canvas Pan', { tag: '@vue-nodes' }, () => {
|
||||
test(
|
||||
'@mobile Can pan with touch',
|
||||
{ tag: '@screenshot' },
|
||||
|
||||
@@ -3,11 +3,9 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Nodes Zoom', () => {
|
||||
test.describe('Vue Nodes Zoom', { tag: '@vue-nodes' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,146 +5,151 @@ import {
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { fitToViewInstant } from '@e2e/helpers/fitToView'
|
||||
|
||||
test.describe('Vue Node Bring to Front', { tag: '@screenshot' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.workflow.loadWorkflow('vueNodes/simple-triple')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
await fitToViewInstant(comfyPage)
|
||||
})
|
||||
test.describe(
|
||||
'Vue Node Bring to Front',
|
||||
{ tag: ['@screenshot', '@vue-nodes'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
await comfyPage.workflow.loadWorkflow('vueNodes/simple-triple')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
await fitToViewInstant(comfyPage)
|
||||
})
|
||||
|
||||
/**
|
||||
* Helper to get the z-index of a node by its title
|
||||
*/
|
||||
async function getNodeZIndex(
|
||||
comfyPage: ComfyPage,
|
||||
title: string
|
||||
): Promise<number> {
|
||||
const node = comfyPage.vueNodes.getNodeByTitle(title)
|
||||
const style = await node.getAttribute('style')
|
||||
const match = style?.match(/z-index:\s*(\d+)/)
|
||||
return match ? parseInt(match[1], 10) : Number.NaN
|
||||
/**
|
||||
* Helper to get the z-index of a node by its title
|
||||
*/
|
||||
async function getNodeZIndex(
|
||||
comfyPage: ComfyPage,
|
||||
title: string
|
||||
): Promise<number> {
|
||||
const node = comfyPage.vueNodes.getNodeByTitle(title)
|
||||
const style = await node.getAttribute('style')
|
||||
const match = style?.match(/z-index:\s*(\d+)/)
|
||||
return match ? parseInt(match[1], 10) : Number.NaN
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the bounding box center of a node
|
||||
*/
|
||||
async function getNodeCenter(
|
||||
comfyPage: ComfyPage,
|
||||
title: string
|
||||
): Promise<{ x: number; y: number }> {
|
||||
const node = comfyPage.vueNodes.getNodeByTitle(title)
|
||||
const box = await node.boundingBox()
|
||||
if (!box) throw new Error(`Node "${title}" not found`)
|
||||
return { x: box.x + box.width / 2, y: box.y + box.height / 2 }
|
||||
}
|
||||
|
||||
test('should bring overlapped node to front when clicking on it', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Get initial positions
|
||||
const clipCenter = await getNodeCenter(comfyPage, 'CLIP Text Encode')
|
||||
const ksamplerHeader = await comfyPage.page
|
||||
.getByText('KSampler')
|
||||
.boundingBox()
|
||||
if (!ksamplerHeader) throw new Error('KSampler header not found')
|
||||
|
||||
// Drag KSampler on top of CLIP Text Encode
|
||||
await comfyPage.canvasOps.dragAndDrop(
|
||||
{ x: ksamplerHeader.x + 50, y: ksamplerHeader.y + 10 },
|
||||
clipCenter
|
||||
)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Screenshot showing KSampler on top of CLIP
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-overlapped-before.png'
|
||||
)
|
||||
|
||||
// KSampler should be on top (higher z-index) after being dragged
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const ksamplerZ = await getNodeZIndex(comfyPage, 'KSampler')
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
return ksamplerZ - clipZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Click on CLIP Text Encode (underneath) - need to click on a visible part
|
||||
// Since KSampler is on top, we click on the edge of CLIP that should still be visible
|
||||
const clipNode = comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode')
|
||||
const clipBox = await clipNode.boundingBox()
|
||||
if (!clipBox) throw new Error('CLIP node not found')
|
||||
|
||||
// Click on a visible edge of CLIP
|
||||
await comfyPage.page.mouse.click(clipBox.x + 30, clipBox.y + 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// CLIP should now be on top - compare post-action z-indices
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
const ksamplerZ = await getNodeZIndex(comfyPage, 'KSampler')
|
||||
return clipZ - ksamplerZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Screenshot showing CLIP now on top
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-overlapped-after.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should bring overlapped node to front when clicking on its widget', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Get CLIP Text Encode position (it has a text widget)
|
||||
const clipCenter = await getNodeCenter(comfyPage, 'CLIP Text Encode')
|
||||
|
||||
// Get VAE Decode position and drag it on top of CLIP
|
||||
const vaeHeader = await comfyPage.page
|
||||
.getByText('VAE Decode')
|
||||
.boundingBox()
|
||||
if (!vaeHeader) throw new Error('VAE Decode header not found')
|
||||
|
||||
await comfyPage.canvasOps.dragAndDrop(
|
||||
{ x: vaeHeader.x + 50, y: vaeHeader.y + 10 },
|
||||
{ x: clipCenter.x - 50, y: clipCenter.y }
|
||||
)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// VAE should be on top after drag
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const vaeZ = await getNodeZIndex(comfyPage, 'VAE Decode')
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
return vaeZ - clipZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Screenshot showing VAE on top
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-widget-overlapped-before.png'
|
||||
)
|
||||
|
||||
// Click on the text widget of CLIP Text Encode
|
||||
const clipNode = comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode')
|
||||
const clipBox = await clipNode.boundingBox()
|
||||
if (!clipBox) throw new Error('CLIP node not found')
|
||||
await comfyPage.page.mouse.click(clipBox.x + 170, clipBox.y + 80)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// CLIP should now be on top - compare post-action z-indices
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
const vaeZ = await getNodeZIndex(comfyPage, 'VAE Decode')
|
||||
return clipZ - vaeZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Screenshot showing CLIP now on top after widget click
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-widget-overlapped-after.png'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the bounding box center of a node
|
||||
*/
|
||||
async function getNodeCenter(
|
||||
comfyPage: ComfyPage,
|
||||
title: string
|
||||
): Promise<{ x: number; y: number }> {
|
||||
const node = comfyPage.vueNodes.getNodeByTitle(title)
|
||||
const box = await node.boundingBox()
|
||||
if (!box) throw new Error(`Node "${title}" not found`)
|
||||
return { x: box.x + box.width / 2, y: box.y + box.height / 2 }
|
||||
}
|
||||
|
||||
test('should bring overlapped node to front when clicking on it', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Get initial positions
|
||||
const clipCenter = await getNodeCenter(comfyPage, 'CLIP Text Encode')
|
||||
const ksamplerHeader = await comfyPage.page
|
||||
.getByText('KSampler')
|
||||
.boundingBox()
|
||||
if (!ksamplerHeader) throw new Error('KSampler header not found')
|
||||
|
||||
// Drag KSampler on top of CLIP Text Encode
|
||||
await comfyPage.canvasOps.dragAndDrop(
|
||||
{ x: ksamplerHeader.x + 50, y: ksamplerHeader.y + 10 },
|
||||
clipCenter
|
||||
)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Screenshot showing KSampler on top of CLIP
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-overlapped-before.png'
|
||||
)
|
||||
|
||||
// KSampler should be on top (higher z-index) after being dragged
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const ksamplerZ = await getNodeZIndex(comfyPage, 'KSampler')
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
return ksamplerZ - clipZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Click on CLIP Text Encode (underneath) - need to click on a visible part
|
||||
// Since KSampler is on top, we click on the edge of CLIP that should still be visible
|
||||
const clipNode = comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode')
|
||||
const clipBox = await clipNode.boundingBox()
|
||||
if (!clipBox) throw new Error('CLIP node not found')
|
||||
|
||||
// Click on a visible edge of CLIP
|
||||
await comfyPage.page.mouse.click(clipBox.x + 30, clipBox.y + 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// CLIP should now be on top - compare post-action z-indices
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
const ksamplerZ = await getNodeZIndex(comfyPage, 'KSampler')
|
||||
return clipZ - ksamplerZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Screenshot showing CLIP now on top
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-overlapped-after.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should bring overlapped node to front when clicking on its widget', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Get CLIP Text Encode position (it has a text widget)
|
||||
const clipCenter = await getNodeCenter(comfyPage, 'CLIP Text Encode')
|
||||
|
||||
// Get VAE Decode position and drag it on top of CLIP
|
||||
const vaeHeader = await comfyPage.page.getByText('VAE Decode').boundingBox()
|
||||
if (!vaeHeader) throw new Error('VAE Decode header not found')
|
||||
|
||||
await comfyPage.canvasOps.dragAndDrop(
|
||||
{ x: vaeHeader.x + 50, y: vaeHeader.y + 10 },
|
||||
{ x: clipCenter.x - 50, y: clipCenter.y }
|
||||
)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// VAE should be on top after drag
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const vaeZ = await getNodeZIndex(comfyPage, 'VAE Decode')
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
return vaeZ - clipZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Screenshot showing VAE on top
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-widget-overlapped-before.png'
|
||||
)
|
||||
|
||||
// Click on the text widget of CLIP Text Encode
|
||||
const clipNode = comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode')
|
||||
const clipBox = await clipNode.boundingBox()
|
||||
if (!clipBox) throw new Error('CLIP node not found')
|
||||
await comfyPage.page.mouse.click(clipBox.x + 170, clipBox.y + 80)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// CLIP should now be on top - compare post-action z-indices
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const clipZ = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
|
||||
const vaeZ = await getNodeZIndex(comfyPage, 'VAE Decode')
|
||||
return clipZ - vaeZ
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
|
||||
// Screenshot showing CLIP now on top after widget click
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'bring-to-front-widget-overlapped-after.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
@@ -60,13 +60,7 @@ async function getNodeRef(comfyPage: ComfyPage, nodeTitle: string) {
|
||||
return refs[0]
|
||||
}
|
||||
|
||||
test.describe('Vue Node Context Menu', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Node Context Menu', { tag: '@vue-nodes' }, () => {
|
||||
test.describe('Single Node Actions', () => {
|
||||
test('should rename node via context menu', async ({ comfyPage }) => {
|
||||
await openContextMenu(comfyPage, 'KSampler')
|
||||
|
||||
@@ -7,11 +7,7 @@ import {
|
||||
getPromotedWidgetCountByName
|
||||
} from '@e2e/helpers/promotedWidgets'
|
||||
|
||||
test.describe('Vue Nodes Image Preview', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
})
|
||||
|
||||
test.describe('Vue Nodes Image Preview', { tag: '@vue-nodes' }, () => {
|
||||
async function loadImageOnNode(comfyPage: ComfyPage) {
|
||||
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
@@ -5,12 +5,7 @@ import {
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import type { Position } from '@e2e/fixtures/types'
|
||||
|
||||
test.describe('Vue Node Moving', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Node Moving', { tag: '@vue-nodes' }, () => {
|
||||
const getHeaderPos = async (
|
||||
comfyPage: ComfyPage,
|
||||
title: string
|
||||
|
||||
@@ -6,146 +6,152 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Vue Nodes - Delete Key Interaction', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// Enable Vue nodes rendering
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', false)
|
||||
await comfyPage.setup()
|
||||
})
|
||||
test.describe(
|
||||
'Vue Nodes - Delete Key Interaction',
|
||||
{ tag: '@vue-nodes' },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', false)
|
||||
await comfyPage.setup()
|
||||
})
|
||||
|
||||
test('Can select all and delete Vue nodes with Delete key', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
test('Can select all and delete Vue nodes with Delete key', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
// Get initial Vue node count
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBeGreaterThan(0)
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
// Get initial Vue node count
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBeGreaterThan(0)
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
|
||||
// Select all Vue nodes
|
||||
await comfyPage.keyboard.selectAll()
|
||||
// Select all Vue nodes
|
||||
await comfyPage.keyboard.selectAll()
|
||||
|
||||
// Verify all Vue nodes are selected
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(initialNodeCount)
|
||||
// Verify all Vue nodes are selected
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(
|
||||
initialNodeCount
|
||||
)
|
||||
|
||||
// Delete with Delete key
|
||||
await comfyPage.vueNodes.deleteSelected()
|
||||
// Delete with Delete key
|
||||
await comfyPage.vueNodes.deleteSelected()
|
||||
|
||||
// Verify all Vue nodes were deleted
|
||||
await expect.poll(() => comfyPage.vueNodes.getNodeCount()).toBe(0)
|
||||
})
|
||||
// Verify all Vue nodes were deleted
|
||||
await expect.poll(() => comfyPage.vueNodes.getNodeCount()).toBe(0)
|
||||
})
|
||||
|
||||
test('Can select specific Vue node and delete it', async ({ comfyPage }) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
test('Can select specific Vue node and delete it', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
// Get initial Vue node count
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBeGreaterThan(0)
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
// Get initial Vue node count
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBeGreaterThan(0)
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
|
||||
// Get first Vue node ID and select it
|
||||
const nodeIds = await comfyPage.vueNodes.getNodeIds()
|
||||
await comfyPage.vueNodes.selectNode(nodeIds[0])
|
||||
// Get first Vue node ID and select it
|
||||
const nodeIds = await comfyPage.vueNodes.getNodeIds()
|
||||
await comfyPage.vueNodes.selectNode(nodeIds[0])
|
||||
|
||||
// Verify selection
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(1)
|
||||
// Verify selection
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(1)
|
||||
|
||||
// Delete with Delete key
|
||||
await comfyPage.vueNodes.deleteSelected()
|
||||
// Delete with Delete key
|
||||
await comfyPage.vueNodes.deleteSelected()
|
||||
|
||||
// Verify one Vue node was deleted
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBe(initialNodeCount - 1)
|
||||
})
|
||||
// Verify one Vue node was deleted
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBe(initialNodeCount - 1)
|
||||
})
|
||||
|
||||
test('Can select and delete Vue node with Backspace key', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
test('Can select and delete Vue node with Backspace key', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
|
||||
// Select first Vue node
|
||||
const nodeIds = await comfyPage.vueNodes.getNodeIds()
|
||||
await comfyPage.vueNodes.selectNode(nodeIds[0])
|
||||
// Select first Vue node
|
||||
const nodeIds = await comfyPage.vueNodes.getNodeIds()
|
||||
await comfyPage.vueNodes.selectNode(nodeIds[0])
|
||||
|
||||
// Delete with Backspace key instead of Delete
|
||||
await comfyPage.vueNodes.deleteSelectedWithBackspace()
|
||||
// Delete with Backspace key instead of Delete
|
||||
await comfyPage.vueNodes.deleteSelectedWithBackspace()
|
||||
|
||||
// Verify Vue node was deleted
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBe(initialNodeCount - 1)
|
||||
})
|
||||
// Verify Vue node was deleted
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBe(initialNodeCount - 1)
|
||||
})
|
||||
|
||||
test('Delete key does not delete node when typing in Vue node widgets', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const initialNodeCount = await comfyPage.nodeOps.getGraphNodesCount()
|
||||
test('Delete key does not delete node when typing in Vue node widgets', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const initialNodeCount = await comfyPage.nodeOps.getGraphNodesCount()
|
||||
|
||||
// Find a text input widget in a Vue node
|
||||
const textWidget = comfyPage.page
|
||||
.locator('input[type="text"], textarea')
|
||||
.first()
|
||||
// Find a text input widget in a Vue node
|
||||
const textWidget = comfyPage.page
|
||||
.locator('input[type="text"], textarea')
|
||||
.first()
|
||||
|
||||
// Click on text widget to focus it
|
||||
await textWidget.click()
|
||||
await textWidget.fill('test text')
|
||||
// Click on text widget to focus it
|
||||
await textWidget.click()
|
||||
await textWidget.fill('test text')
|
||||
|
||||
// Press Delete while focused on widget - should delete text, not node
|
||||
await textWidget.press('Delete')
|
||||
// Press Delete while focused on widget - should delete text, not node
|
||||
await textWidget.press('Delete')
|
||||
|
||||
// Node count should remain the same
|
||||
await expect
|
||||
.poll(() => comfyPage.nodeOps.getGraphNodesCount())
|
||||
.toBe(initialNodeCount)
|
||||
})
|
||||
// Node count should remain the same
|
||||
await expect
|
||||
.poll(() => comfyPage.nodeOps.getGraphNodesCount())
|
||||
.toBe(initialNodeCount)
|
||||
})
|
||||
|
||||
test('Delete key does not delete node when nothing is selected', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
test('Delete key does not delete node when nothing is selected', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
// Ensure no Vue nodes are selected
|
||||
await comfyPage.vueNodes.clearSelection()
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(0)
|
||||
// Ensure no Vue nodes are selected
|
||||
await comfyPage.vueNodes.clearSelection()
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(0)
|
||||
|
||||
// Press Delete key - should not crash and should handle gracefully
|
||||
await comfyPage.page.keyboard.press('Delete')
|
||||
// Press Delete key - should not crash and should handle gracefully
|
||||
await comfyPage.page.keyboard.press('Delete')
|
||||
|
||||
// Vue node count should remain the same
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBeGreaterThan(0)
|
||||
})
|
||||
// Vue node count should remain the same
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('Can multi-select with Ctrl+click and delete multiple Vue nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
test('Can multi-select with Ctrl+click and delete multiple Vue nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
|
||||
// Multi-select first two Vue nodes using Ctrl+click
|
||||
const nodeIds = await comfyPage.vueNodes.getNodeIds()
|
||||
const nodesToSelect = nodeIds.slice(0, 2)
|
||||
await comfyPage.vueNodes.selectNodes(nodesToSelect)
|
||||
// Multi-select first two Vue nodes using Ctrl+click
|
||||
const nodeIds = await comfyPage.vueNodes.getNodeIds()
|
||||
const nodesToSelect = nodeIds.slice(0, 2)
|
||||
await comfyPage.vueNodes.selectNodes(nodesToSelect)
|
||||
|
||||
// Verify expected nodes are selected
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(
|
||||
nodesToSelect.length
|
||||
)
|
||||
// Verify expected nodes are selected
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(
|
||||
nodesToSelect.length
|
||||
)
|
||||
|
||||
// Delete selected Vue nodes
|
||||
await comfyPage.vueNodes.deleteSelected()
|
||||
// Delete selected Vue nodes
|
||||
await comfyPage.vueNodes.deleteSelected()
|
||||
|
||||
// Verify expected nodes were deleted
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBe(initialNodeCount - nodesToSelect.length)
|
||||
})
|
||||
})
|
||||
// Verify expected nodes were deleted
|
||||
await expect
|
||||
.poll(() => comfyPage.vueNodes.getNodeCount())
|
||||
.toBe(initialNodeCount - nodesToSelect.length)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,14 +4,7 @@ import {
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
|
||||
test.describe('Vue Nodes Renaming', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', false)
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Nodes Renaming', { tag: '@vue-nodes' }, () => {
|
||||
test('should display node title', async ({ comfyPage }) => {
|
||||
const vueNode = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
|
||||
await expect(vueNode.header).toContainText('KSampler')
|
||||
|
||||
@@ -3,12 +3,7 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Node Resizing', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Node Resizing', { tag: '@vue-nodes' }, () => {
|
||||
test('should resize node without position drift after selecting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -7,12 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Vue Node Selection', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Node Selection', { tag: '@vue-nodes' }, () => {
|
||||
const modifiers = [
|
||||
{ key: 'Control', name: 'ctrl' },
|
||||
{ key: 'Shift', name: 'shift' },
|
||||
|
||||
@@ -6,13 +6,10 @@ import {
|
||||
const BYPASS_HOTKEY = 'Control+b'
|
||||
const BYPASS_CLASS = /before:bg-bypass\/60/
|
||||
|
||||
test.describe('Vue Node Bypass', () => {
|
||||
test.describe('Vue Node Bypass', { tag: '@vue-nodes' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.Minimap.Visible', false)
|
||||
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test(
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
@@ -3,13 +3,9 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Node Collapse', () => {
|
||||
test.describe('Vue Node Collapse', { tag: '@vue-nodes' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', false)
|
||||
await comfyPage.settings.setSetting('Comfy.EnableTooltips', true)
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should allow collapsing node with collapse icon', async ({
|
||||
|
||||
@@ -4,55 +4,58 @@ import {
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
|
||||
test.describe('Vue Node Custom Colors', { tag: '@screenshot' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('displays color picker button and allows color selection', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = comfyPage.page.locator('[data-node-id]').filter({
|
||||
hasText: 'Load Checkpoint'
|
||||
test.describe(
|
||||
'Vue Node Custom Colors',
|
||||
{ tag: ['@screenshot', '@vue-nodes'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
})
|
||||
await loadCheckpointNode.getByText('Load Checkpoint').click()
|
||||
|
||||
const colorPickerButton = comfyPage.page.getByTestId(
|
||||
TestIds.selectionToolbox.colorPickerButton
|
||||
)
|
||||
await colorPickerButton.click()
|
||||
test('displays color picker button and allows color selection', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = comfyPage.page
|
||||
.locator('[data-node-id]')
|
||||
.filter({
|
||||
hasText: 'Load Checkpoint'
|
||||
})
|
||||
await loadCheckpointNode.getByText('Load Checkpoint').click()
|
||||
|
||||
const colorPickerGroup = comfyPage.page.getByRole('group').filter({
|
||||
has: comfyPage.page.getByTestId(TestIds.selectionToolbox.colorBlue)
|
||||
const colorPickerButton = comfyPage.page.getByTestId(
|
||||
TestIds.selectionToolbox.colorPickerButton
|
||||
)
|
||||
await colorPickerButton.click()
|
||||
|
||||
const colorPickerGroup = comfyPage.page.getByRole('group').filter({
|
||||
has: comfyPage.page.getByTestId(TestIds.selectionToolbox.colorBlue)
|
||||
})
|
||||
await colorPickerGroup
|
||||
.getByTestId(TestIds.selectionToolbox.colorBlue)
|
||||
.click()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-custom-color-blue.png'
|
||||
)
|
||||
})
|
||||
await colorPickerGroup
|
||||
.getByTestId(TestIds.selectionToolbox.colorBlue)
|
||||
.click()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-custom-color-blue.png'
|
||||
)
|
||||
})
|
||||
test('should load node colors from workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.workflow.loadWorkflow('nodes/every_node_color')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-custom-colors-dark-all-colors.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should load node colors from workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.workflow.loadWorkflow('nodes/every_node_color')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-custom-colors-dark-all-colors.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should show brightened node colors on light theme', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.workflow.loadWorkflow('nodes/every_node_color')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-custom-colors-light-all-colors.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
test('should show brightened node colors on light theme', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.workflow.loadWorkflow('nodes/every_node_color')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-custom-colors-light-all-colors.png'
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,12 +5,7 @@ import {
|
||||
|
||||
const ERROR_CLASS = /ring-destructive-background/
|
||||
|
||||
test.describe('Vue Node Error', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Node Error', { tag: '@vue-nodes' }, () => {
|
||||
test('should display error state when node is missing (node from workflow is not installed)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -6,12 +6,7 @@ import {
|
||||
const MUTE_HOTKEY = 'Control+m'
|
||||
const MUTE_OPACITY = '0.5'
|
||||
|
||||
test.describe('Vue Node Mute', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Node Mute', { tag: '@vue-nodes' }, () => {
|
||||
test(
|
||||
'should allow toggling mute on a selected node with hotkey',
|
||||
{ tag: '@screenshot' },
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 107 KiB |
@@ -6,12 +6,7 @@ import {
|
||||
const PIN_HOTKEY = 'p'
|
||||
const PIN_INDICATOR = '[data-testid="node-pin-indicator"]'
|
||||
|
||||
test.describe('Vue Node Pin', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Node Pin', { tag: '@vue-nodes' }, () => {
|
||||
test('should allow toggling pin on a selected node with hotkey', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -3,9 +3,8 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Reroute Node Size', () => {
|
||||
test.describe('Vue Reroute Node Size', { tag: '@vue-nodes' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting('Comfy.Minimap.Visible', false)
|
||||
await comfyPage.workflow.loadWorkflow('links/single_connected_reroute_node')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
@@ -3,9 +3,8 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Advanced Widget Visibility', () => {
|
||||
test.describe('Advanced Widget Visibility', { tag: '@vue-nodes' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Node.AlwaysShowAdvancedWidgets',
|
||||
false
|
||||
|
||||
@@ -6,11 +6,7 @@ import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
type CropValue = { x: number; y: number; width: number; height: number } | null
|
||||
|
||||
test.describe('Image Crop', { tag: '@widget' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
})
|
||||
|
||||
test.describe('Image Crop', { tag: ['@widget', '@vue-nodes'] }, () => {
|
||||
test.describe('without source image', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.workflow.loadWorkflow('widgets/image_crop_widget')
|
||||
|
||||
@@ -3,12 +3,7 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Integer Widget', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
})
|
||||
|
||||
test.describe('Vue Integer Widget', { tag: '@vue-nodes' }, () => {
|
||||
test('should be disabled and not allow changing value when link connected to slot', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -4,12 +4,7 @@ import {
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import { TestIds } from '@e2e/fixtures/selectors'
|
||||
|
||||
test.describe('Vue Upload Widgets', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Upload Widgets', { tag: '@vue-nodes' }, () => {
|
||||
test('should hide canvas-only upload buttons', async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.workflow.loadWorkflow('widgets/all_load_widgets')
|
||||
|
||||
@@ -4,12 +4,7 @@ import {
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Multiline String Widget', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Multiline String Widget', { tag: '@vue-nodes' }, () => {
|
||||
const getFirstClipNode = (comfyPage: ComfyPage) =>
|
||||
comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode (Prompt)').first()
|
||||
|
||||
|
||||
@@ -4,12 +4,7 @@ import {
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import type { TestGraphAccess } from '@e2e/types/globals'
|
||||
|
||||
test.describe('Vue Widget Reactivity', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.describe('Vue Widget Reactivity', { tag: '@vue-nodes' }, () => {
|
||||
test('Should display added widgets', async ({ comfyPage }) => {
|
||||
const loadCheckpointNode = comfyPage.page.locator(
|
||||
'css=[data-testid="node-body-4"] > .lg-node-widgets > div'
|
||||
|
||||
@@ -3,12 +3,8 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Widget copy button', { tag: '@ui' }, () => {
|
||||
test.describe('Widget copy button', { tag: ['@ui', '@vue-nodes'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
|
||||
// Add a PreviewAny node which has a read-only textarea with a copy button
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window.LiteGraph!.createNode('PreviewAny')
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe('Workflow Tab Thumbnails', { tag: '@workflow' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Topbar'
|
||||
|
||||
@@ -58,7 +58,9 @@ const config: KnipConfig = {
|
||||
// Pending integration in stacked PR
|
||||
'src/components/sidebar/tabs/nodeLibrary/CustomNodesPanel.vue',
|
||||
// Agent review check config, not part of the build
|
||||
'.agents/checks/eslint.strict.config.js'
|
||||
'.agents/checks/eslint.strict.config.js',
|
||||
// Public API shim for custom nodes, consumed by comfyAPIPlugin at build time
|
||||
'src/scripts/nodeEvents.ts'
|
||||
],
|
||||
vite: {
|
||||
config: ['vite?(.*).config.mts']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.44.3",
|
||||
"version": "1.44.4",
|
||||
"private": true,
|
||||
"description": "Official front-end implementation of ComfyUI",
|
||||
"homepage": "https://comfy.org",
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useMissingModelStore } from '@/platform/missingModel/missingModelStore'
|
||||
import { useMissingMediaStore } from '@/platform/missingMedia/missingMediaStore'
|
||||
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||
import { useMissingNodesErrorStore } from '@/platform/nodeReplacement/missingNodesErrorStore'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
@@ -41,6 +42,7 @@ import TabErrors from './errors/TabErrors.vue'
|
||||
const canvasStore = useCanvasStore()
|
||||
const executionErrorStore = useExecutionErrorStore()
|
||||
const missingModelStore = useMissingModelStore()
|
||||
const missingMediaStore = useMissingMediaStore()
|
||||
const missingNodesErrorStore = useMissingNodesErrorStore()
|
||||
const rightSidePanelStore = useRightSidePanelStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -58,6 +60,7 @@ const activeMissingNodeGraphIds = computed<Set<string>>(() => {
|
||||
})
|
||||
|
||||
const { activeMissingModelGraphIds } = storeToRefs(missingModelStore)
|
||||
const { activeMissingMediaGraphIds } = storeToRefs(missingMediaStore)
|
||||
|
||||
const { findParentGroup } = useGraphHierarchy()
|
||||
|
||||
@@ -142,13 +145,22 @@ const hasMissingModelSelected = computed(
|
||||
)
|
||||
)
|
||||
|
||||
const hasMissingMediaSelected = computed(
|
||||
() =>
|
||||
hasSelection.value &&
|
||||
selectedNodes.value.some((node) =>
|
||||
activeMissingMediaGraphIds.value.has(String(node.id))
|
||||
)
|
||||
)
|
||||
|
||||
const hasRelevantErrors = computed(() => {
|
||||
if (!hasSelection.value) return hasAnyError.value
|
||||
return (
|
||||
hasDirectNodeError.value ||
|
||||
hasContainerInternalError.value ||
|
||||
hasMissingNodeSelected.value ||
|
||||
hasMissingModelSelected.value
|
||||
hasMissingModelSelected.value ||
|
||||
hasMissingMediaSelected.value
|
||||
)
|
||||
})
|
||||
|
||||
@@ -287,11 +299,14 @@ function handleTitleCancel() {
|
||||
@cancel="handleTitleCancel"
|
||||
@click="isEditing = true"
|
||||
/>
|
||||
<i
|
||||
<button
|
||||
v-if="!isEditing"
|
||||
class="relative top-[2px] ml-2 icon-[lucide--pencil] size-4 shrink-0 cursor-pointer content-center text-muted-foreground hover:text-base-foreground"
|
||||
:aria-label="t('rightSidePanel.editTitle')"
|
||||
class="relative top-[2px] ml-2 size-4 shrink-0 cursor-pointer content-center text-muted-foreground hover:text-base-foreground"
|
||||
@click="isEditing = true"
|
||||
/>
|
||||
>
|
||||
<i aria-hidden="true" class="icon-[lucide--pencil] size-4" />
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ panelTitle }}
|
||||
@@ -304,6 +319,7 @@ function handleTitleCancel() {
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
data-testid="subgraph-editor-toggle"
|
||||
:aria-label="t('rightSidePanel.editSubgraph')"
|
||||
:class="cn(isEditingSubgraph && 'bg-secondary-background-selected')"
|
||||
@click="
|
||||
rightSidePanelStore.openPanel(
|
||||
@@ -338,6 +354,7 @@ function handleTitleCancel() {
|
||||
:key="tab.value"
|
||||
class="px-2 py-1 font-inter text-sm transition-all active:scale-95"
|
||||
:value="tab.value"
|
||||
:data-testid="`panel-tab-${tab.value}`"
|
||||
>
|
||||
{{ tab.label() }}
|
||||
<i
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<Button
|
||||
v-else-if="
|
||||
group.type === 'missing_model' &&
|
||||
downloadableModels.length > 0
|
||||
downloadableModels.length > 1
|
||||
"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user