Compare commits
15 Commits
manual-i18
...
cloud/medi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d193f6e6b5 | ||
|
|
2cfab8a25d | ||
|
|
373266003f | ||
|
|
ee92a89239 | ||
|
|
d59e8b8513 | ||
|
|
72895a7d41 | ||
|
|
4ca3e5a5c7 | ||
|
|
dc12899041 | ||
|
|
e94f7e9d90 | ||
|
|
e9515af00c | ||
|
|
cd96b5147d | ||
|
|
198bacc762 | ||
|
|
219aa2e913 | ||
|
|
0b020cfad3 | ||
|
|
2bb54650b4 |
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<div
|
||||
class="task-div relative grid min-h-52 max-w-48"
|
||||
class="task-div max-w-48 min-h-52 grid relative"
|
||||
:class="{ 'opacity-75': isLoading }"
|
||||
>
|
||||
<Card
|
||||
class="relative h-full max-w-48 overflow-hidden"
|
||||
class="max-w-48 relative h-full overflow-hidden"
|
||||
:class="{ 'opacity-65': runner.state !== 'error' }"
|
||||
v-bind="(({ onClick, ...rest }) => rest)($attrs)"
|
||||
>
|
||||
<template #header>
|
||||
<i
|
||||
v-if="runner.state === 'error'"
|
||||
class="pi pi-exclamation-triangle absolute top-0 -right-14 m-2 text-red-500 opacity-15"
|
||||
class="pi pi-exclamation-triangle text-red-500 absolute m-2 top-0 -right-14 opacity-15"
|
||||
style="font-size: 10rem"
|
||||
/>
|
||||
<img
|
||||
v-if="task.headerImg"
|
||||
:src="task.headerImg"
|
||||
class="h-full w-full object-contain px-4 pt-4 opacity-25"
|
||||
class="object-contain w-full h-full opacity-25 pt-4 px-4"
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
@@ -27,7 +27,7 @@
|
||||
{{ description }}
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="mt-1 flex gap-4">
|
||||
<div class="flex gap-4 mt-1">
|
||||
<Button
|
||||
:icon="task.button?.icon"
|
||||
:label="task.button?.text"
|
||||
@@ -73,7 +73,7 @@ defineEmits<{
|
||||
// Bindings
|
||||
const description = computed(() =>
|
||||
runner.value.state === 'error'
|
||||
? (props.task.errorDescription ?? props.task.shortDescription)
|
||||
? props.task.errorDescription ?? props.task.shortDescription
|
||||
: props.task.shortDescription
|
||||
)
|
||||
|
||||
|
||||
@@ -285,33 +285,6 @@ export class ComfyPage {
|
||||
} = {}) {
|
||||
await this.goto()
|
||||
|
||||
// Mock remote config endpoint for cloud builds
|
||||
// Cloud builds (rh-test) call /api/features on startup, which blocks initialization
|
||||
// if the endpoint doesn't exist or times out. Try real backend first, fallback to empty config.
|
||||
await this.page.route('**/api/features', async (route) => {
|
||||
try {
|
||||
// Try to get response from real backend
|
||||
const response = await route.fetch()
|
||||
if (response.ok()) {
|
||||
await route.fulfill({ response })
|
||||
} else {
|
||||
// Backend doesn't have endpoint, return empty config
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// Network error, return empty config
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Mock release endpoint to prevent changelog popups
|
||||
if (mockReleases) {
|
||||
await this.page.route('**/releases**', async (route) => {
|
||||
|
||||
@@ -7,9 +7,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.skip('should toggle shortcuts panel visibility', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('should toggle shortcuts panel visibility', async ({ comfyPage }) => {
|
||||
// Initially shortcuts panel should be hidden
|
||||
await expect(comfyPage.page.locator('.bottom-panel')).not.toBeVisible()
|
||||
|
||||
@@ -30,9 +28,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
await expect(comfyPage.page.locator('.bottom-panel')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should display essentials shortcuts tab', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('should display essentials shortcuts tab', async ({ comfyPage }) => {
|
||||
// Open shortcuts panel
|
||||
await comfyPage.page
|
||||
.locator('button[aria-label*="Keyboard Shortcuts"]')
|
||||
@@ -66,9 +62,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should display view controls shortcuts tab', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('should display view controls shortcuts tab', async ({ comfyPage }) => {
|
||||
// Open shortcuts panel
|
||||
await comfyPage.page
|
||||
.locator('button[aria-label*="Keyboard Shortcuts"]')
|
||||
@@ -94,7 +88,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should switch between shortcuts tabs', async ({ comfyPage }) => {
|
||||
test('should switch between shortcuts tabs', async ({ comfyPage }) => {
|
||||
// Open shortcuts panel
|
||||
await comfyPage.page
|
||||
.locator('button[aria-label*="Keyboard Shortcuts"]')
|
||||
@@ -128,9 +122,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
).not.toHaveAttribute('aria-selected', 'true')
|
||||
})
|
||||
|
||||
test.skip('should display formatted keyboard shortcuts', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('should display formatted keyboard shortcuts', async ({ comfyPage }) => {
|
||||
// Open shortcuts panel
|
||||
await comfyPage.page
|
||||
.locator('button[aria-label*="Keyboard Shortcuts"]')
|
||||
@@ -152,7 +144,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
expect(hasModifiers).toBeTruthy()
|
||||
})
|
||||
|
||||
test.skip('should maintain panel state when switching to terminal', async ({
|
||||
test('should maintain panel state when switching to terminal', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open shortcuts panel first
|
||||
@@ -180,7 +172,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should handle keyboard navigation', async ({ comfyPage }) => {
|
||||
test('should handle keyboard navigation', async ({ comfyPage }) => {
|
||||
// Open shortcuts panel
|
||||
await comfyPage.page
|
||||
.locator('button[aria-label*="Keyboard Shortcuts"]')
|
||||
@@ -206,7 +198,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
).toHaveAttribute('aria-selected', 'true')
|
||||
})
|
||||
|
||||
test.skip('should close panel by clicking shortcuts button again', async ({
|
||||
test('should close panel by clicking shortcuts button again', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open shortcuts panel
|
||||
@@ -224,7 +216,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
await expect(comfyPage.page.locator('.bottom-panel')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should display shortcuts in organized columns', async ({
|
||||
test('should display shortcuts in organized columns', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open shortcuts panel
|
||||
@@ -259,7 +251,7 @@ test.describe('Bottom Panel Shortcuts', () => {
|
||||
).toHaveAttribute('aria-selected', 'true')
|
||||
})
|
||||
|
||||
test.skip('should open settings dialog when clicking manage shortcuts button', async ({
|
||||
test('should open settings dialog when clicking manage shortcuts button', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open shortcuts panel
|
||||
|
||||
@@ -65,7 +65,7 @@ test.describe('Change Tracker', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Can group multiple change actions into a single transaction', async ({
|
||||
test('Can group multiple change actions into a single transaction', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
@@ -153,7 +153,7 @@ test.describe('Change Tracker', () => {
|
||||
await expect(node).toBeCollapsed()
|
||||
})
|
||||
|
||||
test.skip('Can detect changes in workflow.extra', async ({ comfyPage }) => {
|
||||
test('Can detect changes in workflow.extra', async ({ comfyPage }) => {
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].graph.extra.foo = 'bar'
|
||||
|
||||
@@ -152,7 +152,7 @@ const customColorPalettes: Record<string, Palette> = {
|
||||
}
|
||||
|
||||
test.describe('Color Palette', () => {
|
||||
test.skip('Can show custom color palette', async ({ comfyPage }) => {
|
||||
test('Can show custom color palette', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.CustomColorPalettes', customColorPalettes)
|
||||
// Reload to apply the new setting. Setting Comfy.CustomColorPalettes directly
|
||||
// doesn't update the store immediately.
|
||||
@@ -174,7 +174,7 @@ test.describe('Color Palette', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default-color-palette.png')
|
||||
})
|
||||
|
||||
test.skip('Can add custom color palette', async ({ comfyPage }) => {
|
||||
test('Can add custom color palette', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate((p) => {
|
||||
window['app'].extensionManager.colorPalette.addCustomColorPalette(p)
|
||||
}, customColorPalettes.obsidian_dark)
|
||||
@@ -199,7 +199,7 @@ test.describe('Node Color Adjustments', () => {
|
||||
await comfyPage.loadWorkflow('nodes/every_node_color')
|
||||
})
|
||||
|
||||
test.skip('should adjust opacity via node opacity setting', async ({
|
||||
test('should adjust opacity via node opacity setting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
@@ -217,7 +217,7 @@ test.describe('Node Color Adjustments', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-opacity-1.png')
|
||||
})
|
||||
|
||||
test.skip('should persist color adjustments when changing themes', async ({
|
||||
test('should persist color adjustments when changing themes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.2)
|
||||
@@ -245,7 +245,7 @@ test.describe('Node Color Adjustments', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test.skip('should lighten node colors when switching to light theme', async ({
|
||||
test('should lighten node colors when switching to light theme', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
@@ -261,7 +261,7 @@ test.describe('Node Color Adjustments', () => {
|
||||
await node?.clickContextMenuOption('Colors')
|
||||
})
|
||||
|
||||
test.skip('should persist color adjustments when changing custom node colors', async ({
|
||||
test('should persist color adjustments when changing custom node colors', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page
|
||||
@@ -272,7 +272,7 @@ test.describe('Node Color Adjustments', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('should persist color adjustments when removing custom node color', async ({
|
||||
test('should persist color adjustments when removing custom node color', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page
|
||||
|
||||
@@ -7,7 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Copy Paste', () => {
|
||||
test.skip('Can copy and paste node', async ({ comfyPage }) => {
|
||||
test('Can copy and paste node', async ({ comfyPage }) => {
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.ctrlC()
|
||||
@@ -15,7 +15,7 @@ test.describe('Copy Paste', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-node.png')
|
||||
})
|
||||
|
||||
test.skip('Can copy and paste node with link', async ({ comfyPage }) => {
|
||||
test('Can copy and paste node with link', async ({ comfyPage }) => {
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.ctrlC()
|
||||
@@ -35,7 +35,7 @@ test.describe('Copy Paste', () => {
|
||||
expect(resultString).toBe(originalString + originalString)
|
||||
})
|
||||
|
||||
test.skip('Can copy and paste widget value', async ({ comfyPage }) => {
|
||||
test('Can copy and paste widget value', async ({ comfyPage }) => {
|
||||
// Copy width value (512) from empty latent node to KSampler's seed.
|
||||
// KSampler's seed
|
||||
await comfyPage.canvas.click({
|
||||
@@ -60,7 +60,7 @@ test.describe('Copy Paste', () => {
|
||||
/**
|
||||
* https://github.com/Comfy-Org/ComfyUI_frontend/issues/98
|
||||
*/
|
||||
test.skip('Paste in text area with node previously copied', async ({
|
||||
test('Paste in text area with node previously copied', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
@@ -77,7 +77,7 @@ test.describe('Copy Paste', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Copy text area does not copy node', async ({ comfyPage }) => {
|
||||
test('Copy text area does not copy node', async ({ comfyPage }) => {
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.inputValue()
|
||||
@@ -89,7 +89,7 @@ test.describe('Copy Paste', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('no-node-copied.png')
|
||||
})
|
||||
|
||||
test.skip('Copy node by dragging + alt', async ({ comfyPage }) => {
|
||||
test('Copy node by dragging + alt', async ({ comfyPage }) => {
|
||||
// TextEncodeNode1
|
||||
await comfyPage.page.mouse.move(618, 191)
|
||||
await comfyPage.page.keyboard.down('Alt')
|
||||
|
||||
@@ -27,7 +27,7 @@ test.describe('Custom Icons', () => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.skip('sidebar tab icons use custom SVGs', async ({ comfyPage }) => {
|
||||
test('sidebar tab icons use custom SVGs', async ({ comfyPage }) => {
|
||||
// Find the icon in the sidebar
|
||||
const icon = comfyPage.page.locator(
|
||||
'.icon-\\[comfy--ai-model\\].side-bar-button-icon'
|
||||
|
||||
@@ -35,7 +35,7 @@ test.describe('Load workflow warning', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Does not report warning on undo/redo', async ({ comfyPage }) => {
|
||||
test('Does not report warning on undo/redo', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
|
||||
await comfyPage.loadWorkflow('missing/missing_nodes')
|
||||
@@ -301,9 +301,7 @@ test.describe('Settings', () => {
|
||||
})
|
||||
|
||||
test.describe('Support', () => {
|
||||
test('Should open external zendesk link with OSS tag', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Should open external zendesk link', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
const pagePromise = comfyPage.page.context().waitForEvent('page')
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Support'])
|
||||
@@ -311,10 +309,6 @@ test.describe('Support', () => {
|
||||
|
||||
await newPage.waitForLoadState('networkidle')
|
||||
await expect(newPage).toHaveURL(/.*support\.comfy\.org.*/)
|
||||
|
||||
const url = new URL(newPage.url())
|
||||
expect(url.searchParams.get('tf_42243568391700')).toBe('oss')
|
||||
|
||||
await newPage.close()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,9 +29,7 @@ test.describe('DOM Widget', () => {
|
||||
await expect(lastMultiline).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('Position update when entering focus mode', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Position update when entering focus mode', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.executeCommand('Workspace.ToggleFocusMode')
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 88 KiB |
@@ -7,7 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Execution', () => {
|
||||
test.skip('Report error on unconnected slot', async ({ comfyPage }) => {
|
||||
test('Report error on unconnected slot', async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await comfyPage.clickEmptySpace()
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ test.describe('Graph Canvas Menu', () => {
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
})
|
||||
|
||||
test.skip('Can toggle link visibility', async ({ comfyPage }) => {
|
||||
test('Can toggle link visibility', async ({ comfyPage }) => {
|
||||
const button = comfyPage.page.getByTestId('toggle-link-visibility-button')
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
@@ -39,7 +39,7 @@ test.describe('Graph Canvas Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Focus mode button is clickable and has correct test id', async ({
|
||||
test('Focus mode button is clickable and has correct test id', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const focusButton = comfyPage.page.getByTestId('focus-mode-button')
|
||||
@@ -51,7 +51,7 @@ test.describe('Graph Canvas Menu', () => {
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
test.skip('Zoom controls popup opens and closes', async ({ comfyPage }) => {
|
||||
test('Zoom controls popup opens and closes', async ({ comfyPage }) => {
|
||||
// Find the zoom button by its percentage text content
|
||||
const zoomButton = comfyPage.page.locator('button').filter({
|
||||
hasText: '%'
|
||||
|
||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 100 KiB |
@@ -22,11 +22,11 @@ test.describe('Group Node', () => {
|
||||
await libraryTab.open()
|
||||
})
|
||||
|
||||
test.skip('Is added to node library sidebar', async ({ comfyPage }) => {
|
||||
test('Is added to node library sidebar', async ({ comfyPage }) => {
|
||||
expect(await libraryTab.getFolder('group nodes').count()).toBe(1)
|
||||
})
|
||||
|
||||
test.skip('Can be added to canvas using node library sidebar', async ({
|
||||
test('Can be added to canvas using node library sidebar', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const initialNodeCount = await comfyPage.getGraphNodesCount()
|
||||
@@ -39,7 +39,7 @@ test.describe('Group Node', () => {
|
||||
expect(await comfyPage.getGraphNodesCount()).toBe(initialNodeCount + 1)
|
||||
})
|
||||
|
||||
test.skip('Can be bookmarked and unbookmarked', async ({ comfyPage }) => {
|
||||
test('Can be bookmarked and unbookmarked', async ({ comfyPage }) => {
|
||||
await libraryTab.getFolder(groupNodeCategory).click()
|
||||
await libraryTab
|
||||
.getNode(groupNodeName)
|
||||
@@ -66,7 +66,7 @@ test.describe('Group Node', () => {
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
test.skip('Displays preview on bookmark hover', async ({ comfyPage }) => {
|
||||
test('Displays preview on bookmark hover', async ({ comfyPage }) => {
|
||||
await libraryTab.getFolder(groupNodeCategory).click()
|
||||
await libraryTab
|
||||
.getNode(groupNodeName)
|
||||
@@ -261,14 +261,14 @@ test.describe('Group Node', () => {
|
||||
await groupNode.copy()
|
||||
})
|
||||
|
||||
test.skip('Copies and pastes group node within the same workflow', async ({
|
||||
test('Copies and pastes group node within the same workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.ctrlV()
|
||||
await verifyNodeLoaded(comfyPage, 2)
|
||||
})
|
||||
|
||||
test.skip('Copies and pastes group node after clearing workflow', async ({
|
||||
test('Copies and pastes group node after clearing workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Set setting
|
||||
@@ -281,7 +281,7 @@ test.describe('Group Node', () => {
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
})
|
||||
|
||||
test.skip('Copies and pastes group node into a newly created blank workflow', async ({
|
||||
test('Copies and pastes group node into a newly created blank workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
@@ -289,7 +289,7 @@ test.describe('Group Node', () => {
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
})
|
||||
|
||||
test.skip('Copies and pastes group node across different workflows', async ({
|
||||
test('Copies and pastes group node across different workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
@@ -297,7 +297,7 @@ test.describe('Group Node', () => {
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
})
|
||||
|
||||
test.skip('Serializes group node after copy and paste across workflows', async ({
|
||||
test('Serializes group node after copy and paste across workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
|
||||
@@ -38,7 +38,7 @@ test.describe('History API v2', () => {
|
||||
expect(historyItem.prompt.extra_data).toHaveProperty('client_id')
|
||||
})
|
||||
|
||||
test.skip('Can load workflow from history using history_v2 endpoint', async ({
|
||||
test('Can load workflow from history using history_v2 endpoint', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Simple mock workflow for testing
|
||||
|
||||
@@ -3,10 +3,10 @@ import { expect } from '@playwright/test'
|
||||
import type { Position } from '@vueuse/core'
|
||||
|
||||
import {
|
||||
type ComfyPage,
|
||||
comfyPageFixture as test,
|
||||
testComfySnapToGridGridSize
|
||||
} from '../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
@@ -14,7 +14,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Item Interaction', () => {
|
||||
test.skip('Can select/delete all items', async ({ comfyPage }) => {
|
||||
test('Can select/delete all items', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/mixed_graph_items')
|
||||
await comfyPage.canvas.press('Control+a')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-all.png')
|
||||
@@ -22,9 +22,7 @@ test.describe('Item Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('deleted-all.png')
|
||||
})
|
||||
|
||||
test.skip('Can pin/unpin items with keyboard shortcut', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can pin/unpin items with keyboard shortcut', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/mixed_graph_items')
|
||||
await comfyPage.canvas.press('Control+a')
|
||||
await comfyPage.canvas.press('KeyP')
|
||||
@@ -62,7 +60,7 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('@2x Can highlight selected', async ({ comfyPage }) => {
|
||||
test('@2x Can highlight selected', async ({ comfyPage }) => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default.png')
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-node1.png')
|
||||
@@ -152,7 +150,7 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Can drag node', async ({ comfyPage }) => {
|
||||
test('Can drag node', async ({ comfyPage }) => {
|
||||
await comfyPage.dragNode2()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('dragged-node1.png')
|
||||
})
|
||||
@@ -165,7 +163,7 @@ test.describe('Node Interaction', () => {
|
||||
|
||||
// Test both directions of edge connection.
|
||||
;[{ reverse: false }, { reverse: true }].forEach(({ reverse }) => {
|
||||
test.skip(`Can disconnect/connect edge ${reverse ? 'reverse' : 'normal'}`, async ({
|
||||
test(`Can disconnect/connect edge ${reverse ? 'reverse' : 'normal'}`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
@@ -180,7 +178,7 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Can move link', async ({ comfyPage }) => {
|
||||
test('Can move link', async ({ comfyPage }) => {
|
||||
await comfyPage.dragAndDrop(
|
||||
comfyPage.clipTextEncodeNode1InputSlot,
|
||||
comfyPage.emptySpace
|
||||
@@ -211,7 +209,7 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-link.png')
|
||||
})
|
||||
|
||||
test.skip('Auto snap&highlight when dragging link over node', async ({
|
||||
test('Auto snap&highlight when dragging link over node', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -224,12 +222,12 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Can adjust widget value', async ({ comfyPage }) => {
|
||||
test('Can adjust widget value', async ({ comfyPage }) => {
|
||||
await comfyPage.adjustWidgetValue()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('adjusted-widget-value.png')
|
||||
})
|
||||
|
||||
test.skip('Link snap to slot', async ({ comfyPage }) => {
|
||||
test('Link snap to slot', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('links/snap_to_slot')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('snap_to_slot.png')
|
||||
|
||||
@@ -246,9 +244,7 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('snap_to_slot_linked.png')
|
||||
})
|
||||
|
||||
test.skip('Can batch move links by drag with shift', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can batch move links by drag with shift', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('links/batch_move_links')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('batch_move_links.png')
|
||||
|
||||
@@ -270,7 +266,7 @@ test.describe('Node Interaction', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can batch disconnect links with ctrl+alt+click', async ({
|
||||
test('Can batch disconnect links with ctrl+alt+click', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointClipSlotPos = {
|
||||
@@ -287,7 +283,7 @@ test.describe('Node Interaction', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can toggle dom widget node open/closed', async ({ comfyPage }) => {
|
||||
test('Can toggle dom widget node open/closed', async ({ comfyPage }) => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default.png')
|
||||
await comfyPage.clickTextEncodeNodeToggler()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
@@ -300,7 +296,7 @@ test.describe('Node Interaction', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can close prompt dialog with canvas click (number widget)', async ({
|
||||
test('Can close prompt dialog with canvas click (number widget)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const numberWidgetPos = {
|
||||
@@ -322,7 +318,7 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('prompt-dialog-closed.png')
|
||||
})
|
||||
|
||||
test.skip('Can close prompt dialog with canvas click (text widget)', async ({
|
||||
test('Can close prompt dialog with canvas click (text widget)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const textWidgetPos = {
|
||||
@@ -348,7 +344,7 @@ test.describe('Node Interaction', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can double click node title to edit', async ({ comfyPage }) => {
|
||||
test('Can double click node title to edit', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
await comfyPage.canvas.dblclick({
|
||||
position: {
|
||||
@@ -376,7 +372,7 @@ test.describe('Node Interaction', () => {
|
||||
expect(await comfyPage.page.locator('.node-title-editor').count()).toBe(0)
|
||||
})
|
||||
|
||||
test.skip('Can group selected nodes', async ({ comfyPage }) => {
|
||||
test('Can group selected nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.GroupSelectedNodes.Padding', 10)
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
@@ -389,7 +385,7 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('group-selected-nodes.png')
|
||||
})
|
||||
|
||||
test.skip('Can fit group to contents', async ({ comfyPage }) => {
|
||||
test('Can fit group to contents', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/oversized_group')
|
||||
await comfyPage.ctrlA()
|
||||
await comfyPage.nextFrame()
|
||||
@@ -398,7 +394,7 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('group-fit-to-contents.png')
|
||||
})
|
||||
|
||||
test.skip('Can pin/unpin nodes', async ({ comfyPage }) => {
|
||||
test('Can pin/unpin nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.executeCommand('Comfy.Canvas.ToggleSelectedNodes.Pin')
|
||||
await comfyPage.nextFrame()
|
||||
@@ -408,7 +404,7 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-unpinned.png')
|
||||
})
|
||||
|
||||
test.skip('Can bypass/unbypass nodes with keyboard shortcut', async ({
|
||||
test('Can bypass/unbypass nodes with keyboard shortcut', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.select2Nodes()
|
||||
@@ -422,7 +418,7 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
|
||||
test.describe('Group Interaction', () => {
|
||||
test.skip('Can double click group title to edit', async ({ comfyPage }) => {
|
||||
test('Can double click group title to edit', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/single_group')
|
||||
await comfyPage.canvas.dblclick({
|
||||
position: {
|
||||
@@ -438,21 +434,21 @@ test.describe('Group Interaction', () => {
|
||||
})
|
||||
|
||||
test.describe('Canvas Interaction', () => {
|
||||
test.skip('Can zoom in/out', async ({ comfyPage }) => {
|
||||
test('Can zoom in/out', async ({ comfyPage }) => {
|
||||
await comfyPage.zoom(-100)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-in.png')
|
||||
await comfyPage.zoom(200)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-out.png')
|
||||
})
|
||||
|
||||
test.skip('Can zoom very far out', async ({ comfyPage }) => {
|
||||
test('Can zoom very far out', async ({ comfyPage }) => {
|
||||
await comfyPage.zoom(100, 12)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-very-far-out.png')
|
||||
await comfyPage.zoom(-100, 12)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-back-in.png')
|
||||
})
|
||||
|
||||
test.skip('Can zoom in/out with ctrl+shift+vertical-drag', async ({
|
||||
test('Can zoom in/out with ctrl+shift+vertical-drag', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
@@ -469,7 +465,7 @@ test.describe('Canvas Interaction', () => {
|
||||
await comfyPage.page.keyboard.up('Shift')
|
||||
})
|
||||
|
||||
test.skip('Can zoom in/out after decreasing canvas zoom speed setting', async ({
|
||||
test('Can zoom in/out after decreasing canvas zoom speed setting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.05)
|
||||
@@ -484,7 +480,7 @@ test.describe('Canvas Interaction', () => {
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.1)
|
||||
})
|
||||
|
||||
test.skip('Can zoom in/out after increasing canvas zoom speed', async ({
|
||||
test('Can zoom in/out after increasing canvas zoom speed', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.5)
|
||||
@@ -499,12 +495,12 @@ test.describe('Canvas Interaction', () => {
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.1)
|
||||
})
|
||||
|
||||
test.skip('Can pan', async ({ comfyPage }) => {
|
||||
test('Can pan', async ({ comfyPage }) => {
|
||||
await comfyPage.pan({ x: 200, y: 200 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned.png')
|
||||
})
|
||||
|
||||
test.skip('Cursor style changes when panning', async ({ comfyPage }) => {
|
||||
test('Cursor style changes when panning', async ({ comfyPage }) => {
|
||||
const getCursorStyle = async () => {
|
||||
return await comfyPage.page.evaluate(() => {
|
||||
return (
|
||||
@@ -534,7 +530,7 @@ test.describe('Canvas Interaction', () => {
|
||||
})
|
||||
|
||||
// https://github.com/Comfy-Org/litegraph.js/pull/424
|
||||
test.skip('Properly resets dragging state after pan mode sequence', async ({
|
||||
test('Properly resets dragging state after pan mode sequence', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const getCursorStyle = async () => {
|
||||
@@ -570,10 +566,7 @@ test.describe('Canvas Interaction', () => {
|
||||
expect(await getCursorStyle()).toBe('default')
|
||||
})
|
||||
|
||||
test.skip('Can pan when dragging a link', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
test('Can pan when dragging a link', async ({ comfyPage, comfyMouse }) => {
|
||||
const posSlot1 = comfyPage.clipTextEncodeNode1InputSlot
|
||||
await comfyMouse.move(posSlot1)
|
||||
const posEmpty = comfyPage.emptySpace
|
||||
@@ -593,7 +586,7 @@ test.describe('Canvas Interaction', () => {
|
||||
await comfyMouse.drop()
|
||||
})
|
||||
|
||||
test.skip('Can pan very far and back', async ({ comfyPage }) => {
|
||||
test('Can pan very far and back', async ({ comfyPage }) => {
|
||||
// intentionally slice the edge of where the clip text encode dom widgets are
|
||||
await comfyPage.pan({ x: -800, y: -300 }, { x: 1000, y: 10 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-step-one.png')
|
||||
@@ -609,7 +602,7 @@ test.describe('Canvas Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-back-to-one.png')
|
||||
})
|
||||
|
||||
test.skip('@mobile Can pan with touch', async ({ comfyPage }) => {
|
||||
test('@mobile Can pan with touch', async ({ comfyPage }) => {
|
||||
await comfyPage.closeMenu()
|
||||
await comfyPage.panWithTouch({ x: 200, y: 200 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-touch.png')
|
||||
@@ -643,19 +636,19 @@ test.describe('Widget Interaction', () => {
|
||||
})
|
||||
|
||||
test.describe('Load workflow', () => {
|
||||
test.skip('Can load workflow with string node id', async ({ comfyPage }) => {
|
||||
test('Can load workflow with string node id', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/string_node_id')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('string_node_id.png')
|
||||
})
|
||||
|
||||
test.skip('Can load workflow with ("STRING",) input node', async ({
|
||||
test('Can load workflow with ("STRING",) input node', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('inputs/string_input')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('string_input.png')
|
||||
})
|
||||
|
||||
test.skip('Restore workflow on reload (switch workflow)', async ({
|
||||
test('Restore workflow on reload (switch workflow)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
@@ -664,7 +657,7 @@ test.describe('Load workflow', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler.png')
|
||||
})
|
||||
|
||||
test.skip('Restore workflow on reload (modify workflow)', async ({
|
||||
test('Restore workflow on reload (modify workflow)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
@@ -721,9 +714,7 @@ test.describe('Load workflow', () => {
|
||||
expect(activeWorkflowName).toEqual(workflowB)
|
||||
})
|
||||
|
||||
test.skip('Restores sidebar workflows after reload', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Restores sidebar workflows after reload', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Sidebar'
|
||||
@@ -746,7 +737,7 @@ test.describe('Load workflow', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Auto fit view after loading workflow', async ({ comfyPage }) => {
|
||||
test('Auto fit view after loading workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.EnableWorkflowViewRestore', false)
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler_fit.png')
|
||||
@@ -758,7 +749,7 @@ test.describe('Load duplicate workflow', () => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.skip('A workflow can be loaded multiple times in a row', async ({
|
||||
test('A workflow can be loaded multiple times in a row', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
@@ -847,7 +838,7 @@ test.describe('Canvas Navigation', () => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.NavigationMode', 'legacy')
|
||||
})
|
||||
|
||||
test.skip('Left-click drag in empty area should pan canvas', async ({
|
||||
test('Left-click drag in empty area should pan canvas', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.dragAndDrop({ x: 50, y: 50 }, { x: 150, y: 150 })
|
||||
@@ -856,7 +847,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Middle-click drag should pan canvas', async ({ comfyPage }) => {
|
||||
test('Middle-click drag should pan canvas', async ({ comfyPage }) => {
|
||||
await comfyPage.page.mouse.move(50, 50)
|
||||
await comfyPage.page.mouse.down({ button: 'middle' })
|
||||
await comfyPage.page.mouse.move(150, 150)
|
||||
@@ -867,7 +858,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Mouse wheel should zoom in/out', async ({ comfyPage }) => {
|
||||
test('Mouse wheel should zoom in/out', async ({ comfyPage }) => {
|
||||
await comfyPage.page.mouse.move(400, 300)
|
||||
await comfyPage.page.mouse.wheel(0, -120)
|
||||
await comfyPage.nextFrame()
|
||||
@@ -882,9 +873,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Left-click on node should not pan canvas', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Left-click on node should not pan canvas', async ({ comfyPage }) => {
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
const selectedCount = await comfyPage.getSelectedGraphNodesCount()
|
||||
expect(selectedCount).toBe(1)
|
||||
@@ -899,7 +888,7 @@ test.describe('Canvas Navigation', () => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.NavigationMode', 'standard')
|
||||
})
|
||||
|
||||
test.skip('Left-click drag in empty area should select nodes', async ({
|
||||
test('Left-click drag in empty area should select nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const clipNodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
|
||||
@@ -925,7 +914,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Middle-click drag should pan canvas', async ({ comfyPage }) => {
|
||||
test('Middle-click drag should pan canvas', async ({ comfyPage }) => {
|
||||
await comfyPage.page.mouse.move(50, 50)
|
||||
await comfyPage.page.mouse.down({ button: 'middle' })
|
||||
await comfyPage.page.mouse.move(150, 150)
|
||||
@@ -936,9 +925,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Ctrl + mouse wheel should zoom in/out', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Ctrl + mouse wheel should zoom in/out', async ({ comfyPage }) => {
|
||||
await comfyPage.page.mouse.move(400, 300)
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
await comfyPage.page.mouse.wheel(0, -120)
|
||||
@@ -957,7 +944,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Left-click on node should select node (not start selection box)', async ({
|
||||
test('Left-click on node should select node (not start selection box)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
@@ -968,9 +955,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Space + left-click drag should pan canvas', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Space + left-click drag should pan canvas', async ({ comfyPage }) => {
|
||||
// Click canvas to focus it
|
||||
await comfyPage.page.click('canvas')
|
||||
await comfyPage.nextFrame()
|
||||
@@ -983,7 +968,7 @@ test.describe('Canvas Navigation', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Space key overrides default left-click behavior', async ({
|
||||
test('Space key overrides default left-click behavior', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const clipNodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
|
||||
@@ -1029,7 +1014,7 @@ test.describe('Canvas Navigation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Shift + mouse wheel should pan canvas horizontally', async ({
|
||||
test('Shift + mouse wheel should pan canvas horizontally', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.MouseWheelScroll', 'panning')
|
||||
@@ -1067,7 +1052,7 @@ test.describe('Canvas Navigation', () => {
|
||||
})
|
||||
|
||||
test.describe('Edge Cases', () => {
|
||||
test.skip('Multiple modifier keys work correctly in legacy mode', async ({
|
||||
test('Multiple modifier keys work correctly in legacy mode', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.NavigationMode', 'legacy')
|
||||
|
||||
@@ -27,9 +27,7 @@ test.describe('Canvas Event', () => {
|
||||
// See https://github.com/microsoft/playwright/issues/31580
|
||||
})
|
||||
|
||||
test.skip('Emit litegraph:canvas empty-double-click', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Emit litegraph:canvas empty-double-click', async ({ comfyPage }) => {
|
||||
const eventPromise = comfyPage.page.evaluate(listenForEvent)
|
||||
const doubleClickPromise = comfyPage.doubleClickCanvas()
|
||||
const event = await eventPromise
|
||||
|
||||
@@ -25,7 +25,7 @@ test.describe('Load Workflow in Media', () => {
|
||||
// 'workflow.avif'
|
||||
]
|
||||
fileNames.forEach(async (fileName) => {
|
||||
test.skip(`Load workflow in ${fileName} (drop from filesystem)`, async ({
|
||||
test(`Load workflow in ${fileName} (drop from filesystem)`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.dragAndDropFile(`workflowInMedia/${fileName}`)
|
||||
@@ -37,7 +37,7 @@ test.describe('Load Workflow in Media', () => {
|
||||
'https://comfyanonymous.github.io/ComfyUI_examples/hidream/hidream_dev_example.png'
|
||||
]
|
||||
urls.forEach(async (url) => {
|
||||
test.skip(`Load workflow from URL ${url} (drop from different browser tabs)`, async ({
|
||||
test(`Load workflow from URL ${url} (drop from different browser tabs)`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.dragAndDropURL(url)
|
||||
|
||||
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
@@ -7,7 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('LOD Threshold', () => {
|
||||
test.skip('Should switch to low quality mode at correct zoom threshold', async ({
|
||||
test('Should switch to low quality mode at correct zoom threshold', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Load a workflow with some nodes to render
|
||||
@@ -81,7 +81,7 @@ test.describe('LOD Threshold', () => {
|
||||
expect(zoomedInState.lowQuality).toBe(false)
|
||||
})
|
||||
|
||||
test.skip('Should update threshold when font size setting changes', async ({
|
||||
test('Should update threshold when font size setting changes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
@@ -122,7 +122,7 @@ test.describe('LOD Threshold', () => {
|
||||
expect(afterZoom.lowQuality).toBe(true)
|
||||
})
|
||||
|
||||
test.skip('Should disable LOD when font size is set to 0', async ({
|
||||
test('Should disable LOD when font size is set to 0', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
@@ -149,7 +149,7 @@ test.describe('LOD Threshold', () => {
|
||||
expect(state.scale).toBeLessThan(0.2) // Very zoomed out
|
||||
})
|
||||
|
||||
test.skip('Should show visual difference between LOD on and off', async ({
|
||||
test('Should show visual difference between LOD on and off', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Load a workflow with text-heavy nodes for clear visual difference
|
||||
|
||||
@@ -7,7 +7,7 @@ test.describe('Menu', () => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.skip('Can register sidebar tab', async ({ comfyPage }) => {
|
||||
test('Can register sidebar tab', async ({ comfyPage }) => {
|
||||
const initialChildrenCount = await comfyPage.menu.sideToolbar.evaluate(
|
||||
(el) => el.children.length
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Node Badge', () => {
|
||||
test.skip('Can add badge', async ({ comfyPage }) => {
|
||||
test('Can add badge', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
@@ -26,7 +26,7 @@ test.describe('Node Badge', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-badge.png')
|
||||
})
|
||||
|
||||
test.skip('Can add multiple badges', async ({ comfyPage }) => {
|
||||
test('Can add multiple badges', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
@@ -46,7 +46,7 @@ test.describe('Node Badge', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-badge-multiple.png')
|
||||
})
|
||||
|
||||
test.skip('Can add badge left-side', async ({ comfyPage }) => {
|
||||
test('Can add badge left-side', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
@@ -68,7 +68,7 @@ test.describe('Node Badge', () => {
|
||||
|
||||
test.describe('Node source badge', () => {
|
||||
Object.values(NodeBadgeMode).forEach(async (mode) => {
|
||||
test.skip(`Shows node badges (${mode})`, async ({ comfyPage }) => {
|
||||
test(`Shows node badges (${mode})`, async ({ comfyPage }) => {
|
||||
// Execution error workflow has both custom node and core node.
|
||||
await comfyPage.loadWorkflow('nodes/execution_error')
|
||||
await comfyPage.setSetting('Comfy.NodeBadge.NodeSourceBadgeMode', mode)
|
||||
@@ -81,7 +81,7 @@ test.describe('Node source badge', () => {
|
||||
})
|
||||
|
||||
test.describe('Node badge color', () => {
|
||||
test.skip('Can show node badge with unknown color palette', async ({
|
||||
test('Can show node badge with unknown color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
@@ -97,7 +97,7 @@ test.describe('Node badge color', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can show node badge with light color palette', async ({
|
||||
test('Can show node badge with light color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
@@ -9,27 +9,27 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
// If an input is optional by node definition, it should be shown as
|
||||
// a hollow circle no matter what shape it was defined in the workflow JSON.
|
||||
test.describe('Optional input', () => {
|
||||
test.skip('No shape specified', async ({ comfyPage }) => {
|
||||
test('No shape specified', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/optional_input_no_shape')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('optional_input.png')
|
||||
})
|
||||
|
||||
test.skip('Wrong shape specified', async ({ comfyPage }) => {
|
||||
test('Wrong shape specified', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/optional_input_wrong_shape')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('optional_input.png')
|
||||
})
|
||||
|
||||
test.skip('Correct shape specified', async ({ comfyPage }) => {
|
||||
test('Correct shape specified', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/optional_input_correct_shape')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('optional_input.png')
|
||||
})
|
||||
|
||||
test.skip('Force input', async ({ comfyPage }) => {
|
||||
test('Force input', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/force_input')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('force_input.png')
|
||||
})
|
||||
|
||||
test.skip('Default input', async ({ comfyPage }) => {
|
||||
test('Default input', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/default_input')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default_input.png')
|
||||
})
|
||||
@@ -65,18 +65,18 @@ test.describe('Optional input', () => {
|
||||
const renamedInput = inputs.find((w) => w.name === 'breadth')
|
||||
expect(renamedInput).toBeUndefined()
|
||||
})
|
||||
test.skip('slider', async ({ comfyPage }) => {
|
||||
test('slider', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/simple_slider')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('simple_slider.png')
|
||||
})
|
||||
test.skip('unknown converted widget', async ({ comfyPage }) => {
|
||||
test('unknown converted widget', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.Workflow.ShowMissingNodesWarning', false)
|
||||
await comfyPage.loadWorkflow('missing/missing_nodes_converted_widget')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'missing_nodes_converted_widget.png'
|
||||
)
|
||||
})
|
||||
test.skip('dynamically added input', async ({ comfyPage }) => {
|
||||
test('dynamically added input', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/dynamically_added_input')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'dynamically_added_input.png'
|
||||
|
||||
@@ -27,9 +27,7 @@ test.describe('Node Help', () => {
|
||||
})
|
||||
|
||||
test.describe('Selection Toolbox', () => {
|
||||
test.skip('Should open help menu for selected node', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Should open help menu for selected node', async ({ comfyPage }) => {
|
||||
// Load a workflow with a node
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
await comfyPage.loadWorkflow('default')
|
||||
@@ -66,9 +64,7 @@ test.describe('Node Help', () => {
|
||||
})
|
||||
|
||||
test.describe('Node Library Sidebar', () => {
|
||||
test.skip('Should open help menu from node library', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Should open help menu from node library', async ({ comfyPage }) => {
|
||||
// Open the node library sidebar
|
||||
await comfyPage.menu.nodeLibraryTab.open()
|
||||
|
||||
@@ -101,7 +97,7 @@ test.describe('Node Help', () => {
|
||||
await expect(helpPage.locator('.node-help-content')).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('Should show node library tab when clicking back from help page', async ({
|
||||
test('Should show node library tab when clicking back from help page', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open the node library sidebar
|
||||
@@ -149,7 +145,7 @@ test.describe('Node Help', () => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
})
|
||||
|
||||
test.skip('Should display loading state while fetching help', async ({
|
||||
test('Should display loading state while fetching help', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock slow network response
|
||||
@@ -180,7 +176,7 @@ test.describe('Node Help', () => {
|
||||
await expect(helpPage).toContainText('Test Help Content')
|
||||
})
|
||||
|
||||
test.skip('Should display fallback content when help file not found', async ({
|
||||
test('Should display fallback content when help file not found', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock 404 response for help files
|
||||
@@ -209,7 +205,7 @@ test.describe('Node Help', () => {
|
||||
await expect(helpPage).toContainText('Outputs')
|
||||
})
|
||||
|
||||
test.skip('Should render markdown with images correctly', async ({
|
||||
test('Should render markdown with images correctly', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock response with markdown containing images
|
||||
@@ -255,7 +251,7 @@ test.describe('Node Help', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Should render video elements with source tags in markdown', async ({
|
||||
test('Should render video elements with source tags in markdown', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock response with video elements
|
||||
@@ -316,7 +312,7 @@ test.describe('Node Help', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Should handle custom node documentation paths', async ({
|
||||
test('Should handle custom node documentation paths', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// First load workflow with custom node
|
||||
@@ -369,9 +365,7 @@ This is documentation for a custom node.
|
||||
}
|
||||
})
|
||||
|
||||
test.skip('Should sanitize dangerous HTML content', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Should sanitize dangerous HTML content', async ({ comfyPage }) => {
|
||||
// Mock response with potentially dangerous content
|
||||
await comfyPage.page.route('**/docs/KSampler/en.md', async (route) => {
|
||||
await route.fulfill({
|
||||
@@ -430,7 +424,7 @@ This is documentation for a custom node.
|
||||
await expect(helpPage.locator('img[alt="Safe Image"]')).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('Should handle locale-specific documentation', async ({
|
||||
test('Should handle locale-specific documentation', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock different responses for different locales
|
||||
@@ -474,9 +468,7 @@ This is English documentation.
|
||||
await comfyPage.setSetting('Comfy.Locale', 'en')
|
||||
})
|
||||
|
||||
test.skip('Should handle network errors gracefully', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Should handle network errors gracefully', async ({ comfyPage }) => {
|
||||
// Mock network error
|
||||
await comfyPage.page.route('**/docs/**/*.md', async (route) => {
|
||||
await route.abort('failed')
|
||||
@@ -502,7 +494,7 @@ This is English documentation.
|
||||
expect(content).toBeTruthy()
|
||||
})
|
||||
|
||||
test.skip('Should update help content when switching between nodes', async ({
|
||||
test('Should update help content when switching between nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock different help content for different nodes
|
||||
|
||||
@@ -14,9 +14,7 @@ test.describe('Node search box', () => {
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
})
|
||||
|
||||
test.skip(`Can trigger on empty canvas double click`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test(`Can trigger on empty canvas double click`, async ({ comfyPage }) => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
})
|
||||
@@ -48,14 +46,14 @@ test.describe('Node search box', () => {
|
||||
await expect(comfyPage.searchBox.input).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('Can add node', async ({ comfyPage }) => {
|
||||
test('Can add node', async ({ comfyPage }) => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('added-node.png')
|
||||
})
|
||||
|
||||
test.skip('Can auto link node', async ({ comfyPage }) => {
|
||||
test('Can auto link node', async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
// Select the second item as the first item is always reroute
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('CLIPTextEncode', {
|
||||
@@ -64,7 +62,7 @@ test.describe('Node search box', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('auto-linked-node.png')
|
||||
})
|
||||
|
||||
test.skip('Can auto link batch moved node', async ({ comfyPage }) => {
|
||||
test('Can auto link batch moved node', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('links/batch_move_links')
|
||||
|
||||
const outputSlot1Pos = {
|
||||
@@ -88,7 +86,7 @@ test.describe('Node search box', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Link release connecting to node with no slots', async ({
|
||||
test('Link release connecting to node with no slots', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
@@ -100,9 +98,7 @@ test.describe('Node search box', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Has correct aria-labels on search results', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Has correct aria-labels on search results', async ({ comfyPage }) => {
|
||||
const node = 'Load Checkpoint'
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await comfyPage.searchBox.input.waitFor({ state: 'visible' })
|
||||
@@ -154,7 +150,7 @@ test.describe('Node search box', () => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
})
|
||||
|
||||
test.skip('Can add filter', async ({ comfyPage }) => {
|
||||
test('Can add filter', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
|
||||
await expectFilterChips(comfyPage, ['MODEL'])
|
||||
})
|
||||
@@ -201,13 +197,13 @@ test.describe('Node search box', () => {
|
||||
await expect(comfyPage.searchBox.input).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('Can add multiple filters', async ({ comfyPage }) => {
|
||||
test('Can add multiple filters', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
|
||||
await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
|
||||
await expectFilterChips(comfyPage, ['MODEL', 'CLIP'])
|
||||
})
|
||||
|
||||
test.skip('Can remove filter', async ({ comfyPage }) => {
|
||||
test('Can remove filter', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
|
||||
await comfyPage.searchBox.removeFilter(0)
|
||||
await expectFilterChips(comfyPage, [])
|
||||
@@ -220,7 +216,7 @@ test.describe('Node search box', () => {
|
||||
await comfyPage.searchBox.addFilter('utils', 'Category')
|
||||
})
|
||||
|
||||
test.skip('Can remove first filter', async ({ comfyPage }) => {
|
||||
test('Can remove first filter', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.removeFilter(0)
|
||||
await expectFilterChips(comfyPage, ['CLIP', 'utils'])
|
||||
await comfyPage.searchBox.removeFilter(0)
|
||||
@@ -229,12 +225,12 @@ test.describe('Node search box', () => {
|
||||
await expectFilterChips(comfyPage, [])
|
||||
})
|
||||
|
||||
test.skip('Can remove middle filter', async ({ comfyPage }) => {
|
||||
test('Can remove middle filter', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.removeFilter(1)
|
||||
await expectFilterChips(comfyPage, ['MODEL', 'utils'])
|
||||
})
|
||||
|
||||
test.skip('Can remove last filter', async ({ comfyPage }) => {
|
||||
test('Can remove last filter', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.removeFilter(2)
|
||||
await expectFilterChips(comfyPage, ['MODEL', 'CLIP'])
|
||||
})
|
||||
@@ -246,14 +242,12 @@ test.describe('Node search box', () => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
})
|
||||
|
||||
test.skip('focuses input after adding a filter', async ({ comfyPage }) => {
|
||||
test('focuses input after adding a filter', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
|
||||
await expect(comfyPage.searchBox.input).toHaveFocus()
|
||||
})
|
||||
|
||||
test.skip('focuses input after removing a filter', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('focuses input after removing a filter', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
|
||||
await comfyPage.searchBox.removeFilter(0)
|
||||
await expect(comfyPage.searchBox.input).toHaveFocus()
|
||||
@@ -268,7 +262,7 @@ test.describe('Release context menu', () => {
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
})
|
||||
|
||||
test.skip('Can trigger on link release', async ({ comfyPage }) => {
|
||||
test('Can trigger on link release', async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.nextFrame()
|
||||
@@ -277,7 +271,7 @@ test.describe('Release context menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can search and add node from context menu', async ({
|
||||
test('Can search and add node from context menu', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Note Node', () => {
|
||||
test.skip('Can load node nodes', async ({ comfyPage }) => {
|
||||
test('Can load node nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/note_nodes')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('note_nodes.png')
|
||||
})
|
||||
|
||||
@@ -8,14 +8,14 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Primitive Node', () => {
|
||||
test.skip('Can load with correct size', async ({ comfyPage }) => {
|
||||
test('Can load with correct size', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('primitive/primitive_node')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('primitive_node.png')
|
||||
})
|
||||
|
||||
// When link is dropped on widget, it should automatically convert the widget
|
||||
// to input.
|
||||
test.skip('Can connect to widget', async ({ comfyPage }) => {
|
||||
test('Can connect to widget', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('primitive/primitive_node_unconnected')
|
||||
const primitiveNode: NodeReference = await comfyPage.getNodeRefById(1)
|
||||
const ksamplerNode: NodeReference = await comfyPage.getNodeRefById(2)
|
||||
@@ -26,7 +26,7 @@ test.describe('Primitive Node', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can connect to dom widget', async ({ comfyPage }) => {
|
||||
test('Can connect to dom widget', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow(
|
||||
'primitive/primitive_node_unconnected_dom_widget'
|
||||
)
|
||||
@@ -38,7 +38,7 @@ test.describe('Primitive Node', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can connect to static primitive node', async ({ comfyPage }) => {
|
||||
test('Can connect to static primitive node', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('primitive/static_primitive_unconnected')
|
||||
const primitiveNode: NodeReference = await comfyPage.getNodeRefById(1)
|
||||
const ksamplerNode: NodeReference = await comfyPage.getNodeRefById(2)
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB |
@@ -7,7 +7,7 @@ test.describe('Release Notifications', () => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.skip('should show help center with release information', async ({
|
||||
test('should show help center with release information', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock release API with test data instead of empty array
|
||||
@@ -63,7 +63,7 @@ test.describe('Release Notifications', () => {
|
||||
await expect(helpMenu).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should not show release notifications when mocked (default behavior)', async ({
|
||||
test('should not show release notifications when mocked (default behavior)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Use default setup (mockReleases: true)
|
||||
@@ -94,9 +94,7 @@ test.describe('Release Notifications', () => {
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should handle release API errors gracefully', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('should handle release API errors gracefully', async ({ comfyPage }) => {
|
||||
// Mock API to return an error
|
||||
await comfyPage.page.route('**/releases**', async (route) => {
|
||||
const url = route.request().url()
|
||||
@@ -133,7 +131,7 @@ test.describe('Release Notifications', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should hide "What\'s New" section when notifications are disabled', async ({
|
||||
test('should hide "What\'s New" section when notifications are disabled', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Disable version update notifications
|
||||
@@ -221,7 +219,7 @@ test.describe('Release Notifications', () => {
|
||||
expect(apiCallCount).toBe(0)
|
||||
})
|
||||
|
||||
test.skip('should show "What\'s New" section when notifications are enabled', async ({
|
||||
test('should show "What\'s New" section when notifications are enabled', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Enable version update notifications (default behavior)
|
||||
@@ -274,7 +272,7 @@ test.describe('Release Notifications', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should toggle "What\'s New" section when setting changes', async ({
|
||||
test('should toggle "What\'s New" section when setting changes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Mock release API with test data
|
||||
@@ -329,7 +327,7 @@ test.describe('Release Notifications', () => {
|
||||
await expect(whatsNewSection).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('should handle edge case with empty releases and disabled notifications', async ({
|
||||
test('should handle edge case with empty releases and disabled notifications', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Disable notifications
|
||||
|
||||
@@ -77,7 +77,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
await comfyPage.page.unroute('**/api/models/checkpoints**')
|
||||
})
|
||||
|
||||
test.skip('lazy loads options when widget is added from node library', async ({
|
||||
test('lazy loads options when widget is added from node library', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const nodeName = 'Remote Widget Node'
|
||||
@@ -104,9 +104,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(widgetOptions).toEqual(mockOptions)
|
||||
})
|
||||
|
||||
test.skip('applies query parameters from input spec', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('applies query parameters from input spec', async ({ comfyPage }) => {
|
||||
const nodeName = 'Remote Widget Node With Sort Query Param'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
@@ -115,7 +113,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(widgetOptions).toEqual([...mockOptions].sort())
|
||||
})
|
||||
|
||||
test.skip('handles empty list of options', async ({ comfyPage }) => {
|
||||
test('handles empty list of options', async ({ comfyPage }) => {
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route) => {
|
||||
@@ -130,7 +128,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(widgetOptions).toEqual([])
|
||||
})
|
||||
|
||||
test.skip('falls back to default value when non-200 response', async ({
|
||||
test('falls back to default value when non-200 response', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.route(
|
||||
@@ -167,7 +165,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(requestWasMade).toBe(false)
|
||||
})
|
||||
|
||||
test.skip('fetches options immediately after widget is added to graph', async ({
|
||||
test('fetches options immediately after widget is added to graph', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const requestPromise = comfyPage.page.waitForRequest((request) =>
|
||||
@@ -180,7 +178,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
})
|
||||
|
||||
test.describe('Refresh Behavior', () => {
|
||||
test.skip('refresh button is visible in selection toolbar when node is selected', async ({
|
||||
test('refresh button is visible in selection toolbar when node is selected', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
@@ -199,7 +197,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('refreshes options when TTL expires', async ({ comfyPage }) => {
|
||||
test('refreshes options when TTL expires', async ({ comfyPage }) => {
|
||||
// Fulfill each request with a unique timestamp
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
@@ -230,7 +228,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(refreshedOptions).not.toEqual(initialOptions)
|
||||
})
|
||||
|
||||
test.skip('does not refresh when TTL is not set', async ({ comfyPage }) => {
|
||||
test('does not refresh when TTL is not set', async ({ comfyPage }) => {
|
||||
let requestCount = 0
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
@@ -253,7 +251,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(requestCount).toBe(1) // Should only make initial request
|
||||
})
|
||||
|
||||
test.skip('retries failed requests with backoff', async ({ comfyPage }) => {
|
||||
test('retries failed requests with backoff', async ({ comfyPage }) => {
|
||||
const timestamps: number[] = []
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
@@ -280,9 +278,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(intervals[1]).toBeGreaterThan(intervals[0])
|
||||
})
|
||||
|
||||
test.skip('clicking refresh button forces a refresh', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('clicking refresh button forces a refresh', async ({ comfyPage }) => {
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route) => {
|
||||
@@ -308,7 +304,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
expect(refreshedOptions).not.toEqual(initialOptions)
|
||||
})
|
||||
|
||||
test.skip('control_after_refresh is applied after refresh', async ({
|
||||
test('control_after_refresh is applied after refresh', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const options = [
|
||||
@@ -344,7 +340,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
})
|
||||
|
||||
test.describe('Cache Behavior', () => {
|
||||
test.skip('reuses cached data between widgets with same params', async ({
|
||||
test('reuses cached data between widgets with same params', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
let requestCount = 0
|
||||
|
||||
@@ -12,7 +12,7 @@ test.describe('Reroute Node', () => {
|
||||
await comfyPage.setupWorkflowsDirectory({})
|
||||
})
|
||||
|
||||
test.skip('loads from inserted workflow', async ({ comfyPage }) => {
|
||||
test('loads from inserted workflow', async ({ comfyPage }) => {
|
||||
const workflowName = 'single_connected_reroute_node.json'
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
[workflowName]: 'links/single_connected_reroute_node.json'
|
||||
@@ -44,12 +44,12 @@ test.describe('LiteGraph Native Reroute Node', () => {
|
||||
await comfyPage.setSetting('LiteGraph.Reroute.SplineOffset', 80)
|
||||
})
|
||||
|
||||
test.skip('loads from workflow', async ({ comfyPage }) => {
|
||||
test('loads from workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('reroute/native_reroute')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('native_reroute.png')
|
||||
})
|
||||
|
||||
test.skip('@2x @0.5x Can add reroute by alt clicking on link', async ({
|
||||
test('@2x @0.5x Can add reroute by alt clicking on link', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = (
|
||||
@@ -75,7 +75,7 @@ test.describe('LiteGraph Native Reroute Node', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can add reroute by clicking middle of link context menu', async ({
|
||||
test('Can add reroute by clicking middle of link context menu', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = (
|
||||
@@ -102,7 +102,7 @@ test.describe('LiteGraph Native Reroute Node', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can delete link that is connected to two reroutes', async ({
|
||||
test('Can delete link that is connected to two reroutes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/issues/4695
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 29 KiB |
@@ -8,7 +8,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Canvas Right Click Menu', () => {
|
||||
test.skip('Can add node', async ({ comfyPage }) => {
|
||||
test('Can add node', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickCanvas()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-menu.png')
|
||||
await comfyPage.page.getByText('Add Node').click()
|
||||
@@ -20,7 +20,7 @@ test.describe('Canvas Right Click Menu', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('add-node-node-added.png')
|
||||
})
|
||||
|
||||
test.skip('Can add group', async ({ comfyPage }) => {
|
||||
test('Can add group', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickCanvas()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-menu.png')
|
||||
await comfyPage.page.getByText('Add Group', { exact: true }).click()
|
||||
@@ -28,7 +28,7 @@ test.describe('Canvas Right Click Menu', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('add-group-group-added.png')
|
||||
})
|
||||
|
||||
test.skip('Can convert to group node', async ({ comfyPage }) => {
|
||||
test('Can convert to group node', async ({ comfyPage }) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-2-nodes.png')
|
||||
await comfyPage.rightClickCanvas()
|
||||
@@ -44,7 +44,7 @@ test.describe('Canvas Right Click Menu', () => {
|
||||
})
|
||||
|
||||
test.describe('Node Right Click Menu', () => {
|
||||
test.skip('Can open properties panel', async ({ comfyPage }) => {
|
||||
test('Can open properties panel', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickEmptyLatentNode()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-node.png')
|
||||
await comfyPage.page.getByText('Properties Panel').click()
|
||||
@@ -54,7 +54,7 @@ test.describe('Node Right Click Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can collapse', async ({ comfyPage }) => {
|
||||
test('Can collapse', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickEmptyLatentNode()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-node.png')
|
||||
await comfyPage.page.getByText('Collapse').click()
|
||||
@@ -64,7 +64,7 @@ test.describe('Node Right Click Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can collapse (Node Badge)', async ({ comfyPage }) => {
|
||||
test('Can collapse (Node Badge)', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
@@ -82,7 +82,7 @@ test.describe('Node Right Click Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can bypass', async ({ comfyPage }) => {
|
||||
test('Can bypass', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickEmptyLatentNode()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-node.png')
|
||||
await comfyPage.page.getByText('Bypass').click()
|
||||
@@ -92,7 +92,7 @@ test.describe('Node Right Click Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can pin and unpin', async ({ comfyPage }) => {
|
||||
test('Can pin and unpin', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickEmptyLatentNode()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-node.png')
|
||||
await comfyPage.page.click('.litemenu-entry:has-text("Pin")')
|
||||
@@ -111,7 +111,7 @@ test.describe('Node Right Click Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can move after unpin', async ({ comfyPage }) => {
|
||||
test('Can move after unpin', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickEmptyLatentNode()
|
||||
await comfyPage.page.click('.litemenu-entry:has-text("Pin")')
|
||||
await comfyPage.nextFrame()
|
||||
@@ -125,7 +125,7 @@ test.describe('Node Right Click Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can pin/unpin selected nodes', async ({ comfyPage }) => {
|
||||
test('Can pin/unpin selected nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
await comfyPage.rightClickEmptyLatentNode()
|
||||
|
||||
@@ -15,7 +15,7 @@ test.describe('Selection Toolbox', () => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
})
|
||||
|
||||
test.skip('shows selection toolbox', async ({ comfyPage }) => {
|
||||
test('shows selection toolbox', async ({ comfyPage }) => {
|
||||
// By default, selection toolbox should be enabled
|
||||
await expect(comfyPage.selectionToolbox).not.toBeVisible()
|
||||
|
||||
@@ -30,7 +30,7 @@ test.describe('Selection Toolbox', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('shows at correct position when node is pasted', async ({
|
||||
test('shows at correct position when node is pasted', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
@@ -66,9 +66,7 @@ test.describe('Selection Toolbox', () => {
|
||||
await expect(comfyPage.selectionToolbox).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('shows border only with multiple selections', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('shows border only with multiple selections', async ({ comfyPage }) => {
|
||||
// Select single node
|
||||
await comfyPage.selectNodes(['KSampler'])
|
||||
|
||||
@@ -96,7 +94,7 @@ test.describe('Selection Toolbox', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('displays bypass button in toolbox when nodes are selected', async ({
|
||||
test('displays bypass button in toolbox when nodes are selected', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// A group + a KSampler node
|
||||
|
||||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
@@ -72,7 +72,7 @@ test.describe('Selection Toolbox - More Options Submenus', () => {
|
||||
throw new Error('Could not open More Options menu - popover not showing')
|
||||
}
|
||||
|
||||
test.skip('opens Node Info from More Options menu', async ({ comfyPage }) => {
|
||||
test('opens Node Info from More Options menu', async ({ comfyPage }) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
const nodeInfoButton = comfyPage.page.getByText('Node Info', {
|
||||
exact: true
|
||||
@@ -82,7 +82,7 @@ test.describe('Selection Toolbox - More Options Submenus', () => {
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
test.skip('changes node shape via Shape submenu', async ({ comfyPage }) => {
|
||||
test('changes node shape via Shape submenu', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
const initialShape = await nodeRef.getProperty<number>('shape')
|
||||
|
||||
@@ -99,9 +99,7 @@ test.describe('Selection Toolbox - More Options Submenus', () => {
|
||||
expect(newShape).toBe(1)
|
||||
})
|
||||
|
||||
test.skip('changes node color via Color submenu swatch', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('changes node color via Color submenu swatch', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
const initialColor = await nodeRef.getProperty<string | undefined>('color')
|
||||
|
||||
@@ -119,7 +117,7 @@ test.describe('Selection Toolbox - More Options Submenus', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test.skip('renames a node using Rename action', async ({ comfyPage }) => {
|
||||
test('renames a node using Rename action', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
await openMoreOptions(comfyPage)
|
||||
await comfyPage.page
|
||||
@@ -136,7 +134,7 @@ test.describe('Selection Toolbox - More Options Submenus', () => {
|
||||
expect(newTitle).toBe('RenamedNode')
|
||||
})
|
||||
|
||||
test.skip('closes More Options menu when clicking outside', async ({
|
||||
test('closes More Options menu when clicking outside', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
@@ -153,7 +151,7 @@ test.describe('Selection Toolbox - More Options Submenus', () => {
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('closes More Options menu when clicking the button again (toggle)', async ({
|
||||
test('closes More Options menu when clicking the button again (toggle)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
|
||||
@@ -12,7 +12,7 @@ test.describe('Node library sidebar', () => {
|
||||
await tab.open()
|
||||
})
|
||||
|
||||
test.skip('Node preview and drag to canvas', async ({ comfyPage }) => {
|
||||
test('Node preview and drag to canvas', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
await tab.getFolder('sampling').click()
|
||||
|
||||
@@ -49,7 +49,7 @@ test.describe('Node library sidebar', () => {
|
||||
expect(await comfyPage.getGraphNodesCount()).toBe(count + 1)
|
||||
})
|
||||
|
||||
test.skip('Bookmark node', async ({ comfyPage }) => {
|
||||
test('Bookmark node', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
await tab.getFolder('sampling').click()
|
||||
|
||||
@@ -68,7 +68,7 @@ test.describe('Node library sidebar', () => {
|
||||
expect(await comfyPage.page.isVisible('.node-lib-node-preview')).toBe(true)
|
||||
})
|
||||
|
||||
test.skip('Ignores unrecognized node', async ({ comfyPage }) => {
|
||||
test('Ignores unrecognized node', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo'])
|
||||
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
@@ -76,13 +76,13 @@ test.describe('Node library sidebar', () => {
|
||||
expect(await tab.getNode('foo').count()).toBe(0)
|
||||
})
|
||||
|
||||
test.skip('Displays empty bookmarks folder', async ({ comfyPage }) => {
|
||||
test('Displays empty bookmarks folder', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
expect(await tab.getFolder('foo').count()).toBe(1)
|
||||
})
|
||||
|
||||
test.skip('Can add new bookmark folder', async ({ comfyPage }) => {
|
||||
test('Can add new bookmark folder', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
await tab.newFolderButton.click()
|
||||
const textInput = comfyPage.page.locator('.editable-text input')
|
||||
@@ -95,7 +95,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual(['New Folder/'])
|
||||
})
|
||||
|
||||
test.skip('Can add nested bookmark folder', async ({ comfyPage }) => {
|
||||
test('Can add nested bookmark folder', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
|
||||
@@ -112,7 +112,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual(['foo/', 'foo/bar/'])
|
||||
})
|
||||
|
||||
test.skip('Can delete bookmark folder', async ({ comfyPage }) => {
|
||||
test('Can delete bookmark folder', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
|
||||
@@ -124,7 +124,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
test.skip('Can rename bookmark folder', async ({ comfyPage }) => {
|
||||
test('Can rename bookmark folder', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
|
||||
@@ -140,7 +140,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual(['bar/'])
|
||||
})
|
||||
|
||||
test.skip('Can add bookmark by dragging node to bookmark folder', async ({
|
||||
test('Can add bookmark by dragging node to bookmark folder', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
@@ -155,7 +155,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual(['foo/', 'foo/KSamplerAdvanced'])
|
||||
})
|
||||
|
||||
test.skip('Can add bookmark by clicking bookmark button', async ({
|
||||
test('Can add bookmark by clicking bookmark button', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
@@ -166,9 +166,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual(['KSamplerAdvanced'])
|
||||
})
|
||||
|
||||
test.skip('Can unbookmark node (Top level bookmark)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can unbookmark node (Top level bookmark)', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [
|
||||
'KSamplerAdvanced'
|
||||
])
|
||||
@@ -179,9 +177,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
test.skip('Can unbookmark node (Library node bookmark)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can unbookmark node (Library node bookmark)', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [
|
||||
'KSamplerAdvanced'
|
||||
])
|
||||
@@ -196,7 +192,7 @@ test.describe('Node library sidebar', () => {
|
||||
await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks.V2')
|
||||
).toEqual([])
|
||||
})
|
||||
test.skip('Can customize icon', async ({ comfyPage }) => {
|
||||
test('Can customize icon', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
await tab.getFolder('foo').click({ button: 'right' })
|
||||
@@ -219,7 +215,7 @@ test.describe('Node library sidebar', () => {
|
||||
})
|
||||
})
|
||||
// If color is left as default, it should not be saved
|
||||
test.skip('Can customize icon (default field)', async ({ comfyPage }) => {
|
||||
test('Can customize icon (default field)', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
await tab.getFolder('foo').click({ button: 'right' })
|
||||
@@ -238,7 +234,7 @@ test.describe('Node library sidebar', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Can customize bookmark color after interacting with color options', async ({
|
||||
test('Can customize bookmark color after interacting with color options', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open customization dialog
|
||||
@@ -278,7 +274,7 @@ test.describe('Node library sidebar', () => {
|
||||
await expect(setting['foo/'].color).not.toBe('')
|
||||
})
|
||||
|
||||
test.skip('Can rename customized bookmark folder', async ({ comfyPage }) => {
|
||||
test('Can rename customized bookmark folder', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.BookmarksCustomization', {
|
||||
'foo/': {
|
||||
@@ -307,7 +303,7 @@ test.describe('Node library sidebar', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('Can delete customized bookmark folder', async ({ comfyPage }) => {
|
||||
test('Can delete customized bookmark folder', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.BookmarksCustomization', {
|
||||
'foo/': {
|
||||
@@ -327,7 +323,7 @@ test.describe('Node library sidebar', () => {
|
||||
).toEqual({})
|
||||
})
|
||||
|
||||
test.skip('Can filter nodes in both trees', async ({ comfyPage }) => {
|
||||
test('Can filter nodes in both trees', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [
|
||||
'foo/',
|
||||
'foo/KSamplerAdvanced',
|
||||
|
||||
@@ -16,7 +16,7 @@ test.describe('Workflows sidebar', () => {
|
||||
await comfyPage.setupWorkflowsDirectory({})
|
||||
})
|
||||
|
||||
test.skip('Can create new blank workflow', async ({ comfyPage }) => {
|
||||
test('Can create new blank workflow', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.workflowsTab
|
||||
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
||||
'*Unsaved Workflow.json'
|
||||
@@ -29,7 +29,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can show top level saved workflows', async ({ comfyPage }) => {
|
||||
test('Can show top level saved workflows', async ({ comfyPage }) => {
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
'workflow1.json': 'default.json',
|
||||
'workflow2.json': 'default.json'
|
||||
@@ -42,7 +42,7 @@ test.describe('Workflows sidebar', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can duplicate workflow', async ({ comfyPage }) => {
|
||||
test('Can duplicate workflow', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.workflowsTab
|
||||
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
|
||||
|
||||
@@ -72,7 +72,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can open workflow after insert', async ({ comfyPage }) => {
|
||||
test('Can open workflow after insert', async ({ comfyPage }) => {
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
'workflow1.json': 'nodes/single_ksampler.json'
|
||||
})
|
||||
@@ -91,7 +91,7 @@ test.describe('Workflows sidebar', () => {
|
||||
expect((await comfyPage.getNodes()).length).toEqual(1)
|
||||
})
|
||||
|
||||
test.skip('Can rename nested workflow from opened workflow item', async ({
|
||||
test('Can rename nested workflow from opened workflow item', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
@@ -117,7 +117,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can save workflow as', async ({ comfyPage }) => {
|
||||
test('Can save workflow as', async ({ comfyPage }) => {
|
||||
await comfyPage.executeCommand('Comfy.NewBlankWorkflow')
|
||||
await comfyPage.menu.topbar.saveWorkflowAs('workflow3.json')
|
||||
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
|
||||
@@ -133,7 +133,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Exported workflow does not contain localized slot names', async ({
|
||||
test('Exported workflow does not contain localized slot names', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
@@ -153,7 +153,7 @@ test.describe('Workflows sidebar', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test.skip('Can export same workflow with different locales', async ({
|
||||
test('Can export same workflow with different locales', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
@@ -185,7 +185,7 @@ test.describe('Workflows sidebar', () => {
|
||||
expect(downloadedContent).toEqual(downloadedContentZh)
|
||||
})
|
||||
|
||||
test.skip('Can save workflow as with same name', async ({ comfyPage }) => {
|
||||
test('Can save workflow as with same name', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.topbar.saveWorkflow('workflow5.json')
|
||||
await comfyPage.nextFrame()
|
||||
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
|
||||
@@ -200,7 +200,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can save temporary workflow with unmodified name', async ({
|
||||
test('Can save temporary workflow with unmodified name', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
|
||||
@@ -214,9 +214,7 @@ test.describe('Workflows sidebar', () => {
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
|
||||
})
|
||||
|
||||
test.skip('Can overwrite other workflows with save as', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can overwrite other workflows with save as', async ({ comfyPage }) => {
|
||||
const topbar = comfyPage.menu.topbar
|
||||
await topbar.saveWorkflow('workflow1.json')
|
||||
await topbar.saveWorkflowAs('workflow2.json')
|
||||
@@ -242,7 +240,7 @@ test.describe('Workflows sidebar', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Does not report warning when switching between opened workflows', async ({
|
||||
test('Does not report warning when switching between opened workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('missing/missing_nodes')
|
||||
@@ -260,7 +258,7 @@ test.describe('Workflows sidebar', () => {
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('Can close saved-workflows from the open workflows section', async ({
|
||||
test('Can close saved-workflows from the open workflows section', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.saveWorkflow(
|
||||
@@ -275,7 +273,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can close saved workflow with command', async ({ comfyPage }) => {
|
||||
test('Can close saved workflow with command', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.workflowsTab
|
||||
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
|
||||
await comfyPage.executeCommand('Workspace.CloseWorkflow')
|
||||
@@ -284,9 +282,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can delete workflows (confirm disabled)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can delete workflows (confirm disabled)', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.Workflow.ConfirmDelete', false)
|
||||
|
||||
const { topbar, workflowsTab } = comfyPage.menu
|
||||
@@ -305,7 +301,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can delete workflows', async ({ comfyPage }) => {
|
||||
test('Can delete workflows', async ({ comfyPage }) => {
|
||||
const { topbar, workflowsTab } = comfyPage.menu
|
||||
|
||||
const filename = 'workflow18.json'
|
||||
@@ -323,9 +319,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can duplicate workflow from context menu', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can duplicate workflow from context menu', async ({ comfyPage }) => {
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
'workflow1.json': 'default.json'
|
||||
})
|
||||
@@ -344,9 +338,7 @@ test.describe('Workflows sidebar', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test.skip('Can drop workflow from workflows sidebar', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can drop workflow from workflows sidebar', async ({ comfyPage }) => {
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
'workflow1.json': 'default.json'
|
||||
})
|
||||
|
||||
@@ -468,9 +468,7 @@ test.describe('Subgraph Operations', () => {
|
||||
expect(finalNodeCount).toBe(initialNodeCount + 1)
|
||||
})
|
||||
|
||||
test.skip('Can undo and redo operations in subgraph', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('Can undo and redo operations in subgraph', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('subgraphs/basic-subgraph')
|
||||
|
||||
const subgraphNode = await comfyPage.getNodeRefById('2')
|
||||
@@ -685,7 +683,7 @@ test.describe('Subgraph Operations', () => {
|
||||
expect(widgetCount).toBe(0)
|
||||
})
|
||||
|
||||
test.skip('Multiple promoted widgets are handled correctly', async ({
|
||||
test('Multiple promoted widgets are handled correctly', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow(
|
||||
|
||||
@@ -69,7 +69,7 @@ test.describe('Templates', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test.skip('Can load template workflows', async ({ comfyPage }) => {
|
||||
test('Can load template workflows', async ({ comfyPage }) => {
|
||||
// Clear the workflow
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
await comfyPage.executeCommand('Comfy.NewBlankWorkflow')
|
||||
|
||||
@@ -12,9 +12,7 @@ test.describe('Vue Node Groups', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.skip('should allow creating groups with hotkey', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('should allow creating groups with hotkey', async ({ comfyPage }) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.getByText('KSampler').click({ modifiers: ['Control'] })
|
||||
await comfyPage.page.keyboard.press(CREATE_GROUP_HOTKEY)
|
||||
@@ -24,7 +22,7 @@ test.describe('Vue Node Groups', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('should allow fitting group to contents', async ({ comfyPage }) => {
|
||||
test('should allow fitting group to contents', async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('groups/oversized_group')
|
||||
await comfyPage.ctrlA()
|
||||
|
||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 47 KiB |
@@ -9,7 +9,7 @@ test.describe('Vue Nodes Canvas Pan', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.skip('@mobile Can pan with touch', async ({ comfyPage }) => {
|
||||
test('@mobile Can pan with touch', async ({ comfyPage }) => {
|
||||
await comfyPage.panWithTouch({ x: 64, y: 64 }, { x: 256, y: 256 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-paned-with-touch.png'
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 11 KiB |
@@ -9,7 +9,7 @@ test.describe('Vue Nodes Zoom', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.skip('should not capture drag while zooming with ctrl+shift+drag', async ({
|
||||
test('should not capture drag while zooming with ctrl+shift+drag', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 14 KiB |
@@ -109,7 +109,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
await fitToViewInstant(comfyPage)
|
||||
})
|
||||
|
||||
test.skip('should show a link dragging out from a slot when dragging on a slot', async ({
|
||||
test('should show a link dragging out from a slot when dragging on a slot', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -218,7 +218,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
expect(await samplerInput.getLinkCount()).toBe(0)
|
||||
})
|
||||
|
||||
test.skip('should reuse the existing origin when dragging an input link', async ({
|
||||
test('should reuse the existing origin when dragging an input link', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -255,7 +255,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
await comfyMouse.drop()
|
||||
})
|
||||
|
||||
test.skip('ctrl+alt drag from an input starts a fresh link', async ({
|
||||
test('ctrl+alt drag from an input starts a fresh link', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -395,7 +395,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
expect(await vaeInput.getLinkCount()).toBe(1)
|
||||
})
|
||||
|
||||
test.skip('rerouted input drag preview remains anchored to reroute', async ({
|
||||
test('rerouted input drag preview remains anchored to reroute', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -480,7 +480,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
expect(linkDetails?.parentId).not.toBeNull()
|
||||
})
|
||||
|
||||
test.skip('rerouted output shift-drag preview remains anchored to reroute', async ({
|
||||
test('rerouted output shift-drag preview remains anchored to reroute', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -639,7 +639,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('shift-dragging an output with multiple links should drag all links', async ({
|
||||
test('shift-dragging an output with multiple links should drag all links', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -694,7 +694,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test.skip('should snap to node center while dragging and link on drop', async ({
|
||||
test('should snap to node center while dragging and link on drop', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
@@ -743,7 +743,7 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
expect(linked?.targetId).toBe(samplerNode.id)
|
||||
})
|
||||
|
||||
test.skip('should snap to a specific compatible slot when targeting it', async ({
|
||||
test('should snap to a specific compatible slot when targeting it', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
type ComfyPage,
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../../../../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '../../../../fixtures/ComfyPage'
|
||||
import type { Position } from '../../../../fixtures/types'
|
||||
|
||||
test.describe('Vue Node Moving', () => {
|
||||
@@ -29,7 +29,7 @@ test.describe('Vue Node Moving', () => {
|
||||
expect(diffY).toBeGreaterThan(0)
|
||||
}
|
||||
|
||||
test.skip('should allow moving nodes by dragging', async ({ comfyPage }) => {
|
||||
test('should allow moving nodes by dragging', async ({ comfyPage }) => {
|
||||
const loadCheckpointHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await comfyPage.dragAndDrop(loadCheckpointHeaderPos, {
|
||||
x: 256,
|
||||
@@ -42,7 +42,7 @@ test.describe('Vue Node Moving', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-node-moved-node.png')
|
||||
})
|
||||
|
||||
test.skip('@mobile should allow moving nodes by dragging on touch devices', async ({
|
||||
test('@mobile should allow moving nodes by dragging on touch devices', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Disable minimap (gets in way of the node on small screens)
|
||||
|
||||
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 96 KiB |
@@ -11,7 +11,7 @@ test.describe('Vue Node Custom Colors', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.skip('displays color picker button and allows color selection', async ({
|
||||
test('displays color picker button and allows color selection', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = comfyPage.page.locator('[data-node-id]').filter({
|
||||
@@ -30,14 +30,14 @@ test.describe('Vue Node Custom Colors', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('should load node colors from workflow', async ({ comfyPage }) => {
|
||||
test('should load node colors from workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/every_node_color')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-custom-colors-dark-all-colors.png'
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('should show brightened node colors on light theme', async ({
|
||||
test('should show brightened node colors on light theme', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 76 KiB |
@@ -13,9 +13,7 @@ test.describe('Vue Nodes - LOD', () => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
})
|
||||
|
||||
test.skip('should toggle LOD based on zoom threshold', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
|
||||
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 97 KiB |
@@ -9,7 +9,7 @@ test.describe('Vue Upload Widgets', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test.skip('should hide canvas-only upload buttons', async ({ comfyPage }) => {
|
||||
test('should hide canvas-only upload buttons', async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('widgets/all_load_widgets')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 66 KiB |
@@ -7,7 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
})
|
||||
|
||||
test.describe('Combo text widget', () => {
|
||||
test.skip('Truncates text when resized', async ({ comfyPage }) => {
|
||||
test('Truncates text when resized', async ({ comfyPage }) => {
|
||||
await comfyPage.resizeLoadCheckpointNode(0.2, 1)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'load-checkpoint-resized-min-width.png'
|
||||
@@ -19,16 +19,14 @@ test.describe('Combo text widget', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip("Doesn't truncate when space still available", async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
test("Doesn't truncate when space still available", async ({ comfyPage }) => {
|
||||
await comfyPage.resizeEmptyLatentNode(0.8, 0.8)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'empty-latent-resized-80-percent.png'
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('Can revert to full text', async ({ comfyPage }) => {
|
||||
test('Can revert to full text', async ({ comfyPage }) => {
|
||||
await comfyPage.resizeLoadCheckpointNode(0.8, 1, true)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('resized-to-original.png')
|
||||
})
|
||||
@@ -82,7 +80,7 @@ test.describe('Combo text widget', () => {
|
||||
})
|
||||
|
||||
test.describe('Boolean widget', () => {
|
||||
test.skip('Can toggle', async ({ comfyPage }) => {
|
||||
test('Can toggle', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/boolean_widget')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('boolean_widget.png')
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
@@ -95,7 +93,7 @@ test.describe('Boolean widget', () => {
|
||||
})
|
||||
|
||||
test.describe('Slider widget', () => {
|
||||
test.skip('Can drag adjust value', async ({ comfyPage }) => {
|
||||
test('Can drag adjust value', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/simple_slider')
|
||||
await comfyPage.page.waitForTimeout(300)
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
@@ -117,7 +115,7 @@ test.describe('Slider widget', () => {
|
||||
})
|
||||
|
||||
test.describe('Number widget', () => {
|
||||
test.skip('Can drag adjust value', async ({ comfyPage }) => {
|
||||
test('Can drag adjust value', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/seed_widget')
|
||||
await comfyPage.page.waitForTimeout(300)
|
||||
|
||||
@@ -139,7 +137,7 @@ test.describe('Number widget', () => {
|
||||
})
|
||||
|
||||
test.describe('Dynamic widget manipulation', () => {
|
||||
test.skip('Auto expand node when widget is added dynamically', async ({
|
||||
test('Auto expand node when widget is added dynamically', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
@@ -155,12 +153,12 @@ test.describe('Dynamic widget manipulation', () => {
|
||||
})
|
||||
|
||||
test.describe('Image widget', () => {
|
||||
test.skip('Can load image', async ({ comfyPage }) => {
|
||||
test('Can load image', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_image_widget')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('load_image_widget.png')
|
||||
})
|
||||
|
||||
test.skip('Can drag and drop image', async ({ comfyPage }) => {
|
||||
test('Can drag and drop image', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_image_widget')
|
||||
|
||||
// Get position of the load image node
|
||||
@@ -184,7 +182,7 @@ test.describe('Image widget', () => {
|
||||
expect(filename).toBe('image32x32.webp')
|
||||
})
|
||||
|
||||
test.skip('Can change image by changing the filename combo value', async ({
|
||||
test('Can change image by changing the filename combo value', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_image_widget')
|
||||
@@ -322,7 +320,7 @@ test.describe('Animated image widget', () => {
|
||||
})
|
||||
|
||||
test.describe('Load audio widget', () => {
|
||||
test.skip('Can load audio', async ({ comfyPage }) => {
|
||||
test('Can load audio', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_audio_widget')
|
||||
// Wait for the audio widget to be rendered in the DOM
|
||||
await comfyPage.page.waitForSelector('.comfy-audio', { state: 'attached' })
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
||||
import pluginJs from '@eslint/js'
|
||||
import pluginI18n from '@intlify/eslint-plugin-vue-i18n'
|
||||
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
|
||||
import { importX } from 'eslint-plugin-import-x'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import storybook from 'eslint-plugin-storybook'
|
||||
@@ -24,17 +23,10 @@ const commonGlobals = {
|
||||
} as const
|
||||
|
||||
const settings = {
|
||||
'import-x/resolver-next': [
|
||||
createTypeScriptImportResolver({
|
||||
alwaysTryTypes: true,
|
||||
project: [
|
||||
'./tsconfig.json',
|
||||
'./apps/*/tsconfig.json',
|
||||
'./packages/*/tsconfig.json'
|
||||
],
|
||||
noWarnOnMultipleProjects: true
|
||||
})
|
||||
],
|
||||
'import/resolver': {
|
||||
typescript: true,
|
||||
node: true
|
||||
},
|
||||
tailwindcss: {
|
||||
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
|
||||
functions: ['cn', 'clsx', 'tw']
|
||||
@@ -76,7 +68,9 @@ export default defineConfig([
|
||||
projectService: {
|
||||
allowDefaultProject: [
|
||||
'vite.electron.config.mts',
|
||||
'vite.types.config.mts'
|
||||
'vite.types.config.mts',
|
||||
'playwright.config.ts',
|
||||
'playwright.i18n.config.ts'
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -254,17 +248,5 @@ export default defineConfig([
|
||||
rules: {
|
||||
'no-console': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['scripts/**/*.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
'no-console': 'off'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
15
global.d.ts
vendored
@@ -4,19 +4,12 @@ declare const __SENTRY_DSN__: string
|
||||
declare const __ALGOLIA_APP_ID__: string
|
||||
declare const __ALGOLIA_API_KEY__: string
|
||||
declare const __USE_PROD_CONFIG__: boolean
|
||||
declare const __MIXPANEL_TOKEN__: string
|
||||
|
||||
interface Window {
|
||||
__CONFIG__: {
|
||||
mixpanel_token?: string
|
||||
subscription_required?: boolean
|
||||
server_health_alert?: {
|
||||
message: string
|
||||
tooltip?: string
|
||||
severity?: 'info' | 'warning' | 'error'
|
||||
badge?: string
|
||||
}
|
||||
}
|
||||
type BuildFeatureFlags = {
|
||||
REQUIRE_SUBSCRIPTION: boolean
|
||||
}
|
||||
declare const __BUILD_FLAGS__: BuildFeatureFlags
|
||||
|
||||
interface Navigator {
|
||||
/**
|
||||
|
||||
@@ -1065,7 +1065,7 @@ audio.comfy-audio.empty-audio-widget {
|
||||
}
|
||||
|
||||
.isLOD .lg-node-header {
|
||||
border-radius: 0;
|
||||
border-radius: 0px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
5
packages/design-system/src/icons/image-ai-edit.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 9.99996L11.9427 7.94263C11.6926 7.69267 11.3536 7.55225 11 7.55225C10.6464 7.55225 10.3074 7.69267 10.0573 7.94263L9 9M8 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.51377 12.671L4.77612 14.3921C4.67222 14.6346 4.32853 14.6346 4.22463 14.3921L3.48699 12.671C3.45664 12.6002 3.40022 12.5437 3.32942 12.5134L1.60825 11.7757C1.36581 11.6718 1.36581 11.3282 1.60825 11.2243L3.32942 10.4866C3.40022 10.4563 3.45664 10.3998 3.48699 10.329L4.22463 8.60787C4.32853 8.36544 4.67222 8.36544 4.77612 8.60787L5.51377 10.329C5.54411 10.3998 5.60053 10.4563 5.67134 10.4866L7.39251 11.2243C7.63494 11.3282 7.63494 11.6718 7.39251 11.7757L5.67134 12.5134C5.60053 12.5437 5.54411 12.6002 5.51377 12.671Z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
||||
<path d="M5 5H5.0001" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -474,3 +474,93 @@ export function formatDuration(milliseconds: number): string {
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
// Module scope constants to avoid re-initialization on every call
|
||||
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp']
|
||||
const VIDEO_EXTENSIONS = ['mp4', 'webm', 'mov', 'avi']
|
||||
const AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'flac']
|
||||
const THREE_D_EXTENSIONS = ['obj', 'fbx', 'gltf', 'glb']
|
||||
|
||||
/**
|
||||
* Truncates a filename while preserving the extension
|
||||
* @param filename The filename to truncate
|
||||
* @param maxLength Maximum length for the filename without extension
|
||||
* @returns Truncated filename with extension preserved
|
||||
*/
|
||||
export function truncateFilename(
|
||||
filename: string,
|
||||
maxLength: number = 20
|
||||
): string {
|
||||
if (!filename || filename.length <= maxLength) {
|
||||
return filename
|
||||
}
|
||||
|
||||
const lastDotIndex = filename.lastIndexOf('.')
|
||||
const nameWithoutExt =
|
||||
lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename
|
||||
const extension = lastDotIndex > -1 ? filename.substring(lastDotIndex) : ''
|
||||
|
||||
// If the name without extension is short enough, return as is
|
||||
if (nameWithoutExt.length <= maxLength) {
|
||||
return filename
|
||||
}
|
||||
|
||||
// Calculate how to split the truncation
|
||||
const halfLength = Math.floor((maxLength - 3) / 2) // -3 for '...'
|
||||
const start = nameWithoutExt.substring(0, halfLength)
|
||||
const end = nameWithoutExt.substring(nameWithoutExt.length - halfLength)
|
||||
|
||||
return `${start}...${end}${extension}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the media type from a filename's extension (singular form)
|
||||
* @param filename The filename to analyze
|
||||
* @returns The media type: 'image', 'video', 'audio', or '3D'
|
||||
*/
|
||||
export function getMediaTypeFromFilename(
|
||||
filename: string
|
||||
): 'image' | 'video' | 'audio' | '3D' {
|
||||
if (!filename) return 'image'
|
||||
const ext = filename.split('.').pop()?.toLowerCase()
|
||||
if (!ext) return 'image'
|
||||
|
||||
if (IMAGE_EXTENSIONS.includes(ext)) return 'image'
|
||||
if (VIDEO_EXTENSIONS.includes(ext)) return 'video'
|
||||
if (AUDIO_EXTENSIONS.includes(ext)) return 'audio'
|
||||
if (THREE_D_EXTENSIONS.includes(ext)) return '3D'
|
||||
|
||||
return 'image'
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getMediaTypeFromFilename instead - returns plural form for legacy compatibility
|
||||
* @param filename The filename to analyze
|
||||
* @returns The media type in plural form: 'images', 'videos', 'audios', '3D'
|
||||
*/
|
||||
export function getMediaTypeFromFilenamePlural(filename: string): string {
|
||||
const type = getMediaTypeFromFilename(filename)
|
||||
switch (type) {
|
||||
case 'image':
|
||||
return 'images'
|
||||
case 'video':
|
||||
return 'videos'
|
||||
case 'audio':
|
||||
return 'audios'
|
||||
case '3D':
|
||||
return '3D'
|
||||
default:
|
||||
return 'images'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getMediaTypeFromFilename instead - kept for backward compatibility
|
||||
* @param filename The filename to analyze
|
||||
* @returns The media kind: 'image', 'video', 'audio', or '3D'
|
||||
*/
|
||||
export function getMediaKindFromFilename(
|
||||
filename: string
|
||||
): 'image' | 'video' | 'audio' | '3D' {
|
||||
return getMediaTypeFromFilename(filename)
|
||||
}
|
||||
|
||||
64
pnpm-lock.yaml
generated
@@ -183,12 +183,18 @@ catalogs:
|
||||
lint-staged:
|
||||
specifier: ^15.2.7
|
||||
version: 15.2.7
|
||||
markdown-table:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4
|
||||
mixpanel-browser:
|
||||
specifier: ^2.71.0
|
||||
version: 2.71.0
|
||||
nx:
|
||||
specifier: 21.4.1
|
||||
version: 21.4.1
|
||||
picocolors:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
pinia:
|
||||
specifier: ^2.1.7
|
||||
version: 2.2.2
|
||||
@@ -196,14 +202,20 @@ catalogs:
|
||||
specifier: ^1.8.0
|
||||
version: 1.8.0
|
||||
prettier:
|
||||
specifier: ^3.3.2
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
pretty-bytes:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
primeicons:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
primevue:
|
||||
specifier: ^4.2.5
|
||||
version: 4.2.5
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^6.0.4
|
||||
version: 6.0.4
|
||||
storybook:
|
||||
specifier: ^9.1.6
|
||||
version: 9.1.6
|
||||
@@ -585,18 +597,30 @@ importers:
|
||||
lint-staged:
|
||||
specifier: 'catalog:'
|
||||
version: 15.2.7
|
||||
markdown-table:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.4
|
||||
mixpanel-browser:
|
||||
specifier: 'catalog:'
|
||||
version: 2.71.0
|
||||
nx:
|
||||
specifier: 'catalog:'
|
||||
version: 21.4.1
|
||||
picocolors:
|
||||
specifier: 'catalog:'
|
||||
version: 1.1.1
|
||||
postcss-html:
|
||||
specifier: 'catalog:'
|
||||
version: 1.8.0
|
||||
prettier:
|
||||
specifier: 'catalog:'
|
||||
version: 3.6.2
|
||||
pretty-bytes:
|
||||
specifier: 'catalog:'
|
||||
version: 7.1.0
|
||||
rollup-plugin-visualizer:
|
||||
specifier: 'catalog:'
|
||||
version: 6.0.4(rollup@4.22.4)
|
||||
storybook:
|
||||
specifier: 'catalog:'
|
||||
version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
@@ -746,11 +770,11 @@ importers:
|
||||
packages/shared-frontend-utils:
|
||||
dependencies:
|
||||
axios:
|
||||
specifier: ^1.11.0
|
||||
specifier: 'catalog:'
|
||||
version: 1.11.0
|
||||
devDependencies:
|
||||
typescript:
|
||||
specifier: ^5.9.2
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.2
|
||||
|
||||
packages/tailwind-utils:
|
||||
@@ -6388,6 +6412,10 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-bytes@7.1.0:
|
||||
resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
pretty-format@27.5.1:
|
||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
@@ -6690,6 +6718,19 @@ packages:
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
rollup-plugin-visualizer@6.0.4:
|
||||
resolution: {integrity: sha512-q8Q7J/6YofkmaGW1sH/fPRAz37x/+pd7VBuaUU7lwvOS/YikuiiEU9jeb9PH8XHiq50XFrUsBbOxeAMYQ7KZkg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
rolldown: 1.x || ^1.0.0-beta
|
||||
rollup: 2.x || 3.x || 4.x
|
||||
peerDependenciesMeta:
|
||||
rolldown:
|
||||
optional: true
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
rollup@4.22.4:
|
||||
resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
@@ -6844,6 +6885,10 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.7.6:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
speakingurl@14.0.1:
|
||||
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -14326,6 +14371,8 @@ snapshots:
|
||||
|
||||
prettier@3.6.2: {}
|
||||
|
||||
pretty-bytes@7.1.0: {}
|
||||
|
||||
pretty-format@27.5.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
@@ -14773,6 +14820,15 @@ snapshots:
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rollup-plugin-visualizer@6.0.4(rollup@4.22.4):
|
||||
dependencies:
|
||||
open: 8.4.2
|
||||
picomatch: 4.0.3
|
||||
source-map: 0.7.6
|
||||
yargs: 17.7.2
|
||||
optionalDependencies:
|
||||
rollup: 4.22.4
|
||||
|
||||
rollup@4.22.4:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
@@ -14964,6 +15020,8 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
@@ -1,64 +1,29 @@
|
||||
/**
|
||||
* Utility functions for downloading files
|
||||
*/
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
// Constants
|
||||
const DEFAULT_DOWNLOAD_FILENAME = 'download.png'
|
||||
|
||||
/**
|
||||
* Trigger a download by creating a temporary anchor element
|
||||
* @param href - The URL or blob URL to download
|
||||
* @param filename - The filename to suggest to the browser
|
||||
*/
|
||||
function triggerLinkDownload(href: string, filename: string): void {
|
||||
const link = document.createElement('a')
|
||||
link.href = href
|
||||
link.download = filename
|
||||
link.style.display = 'none'
|
||||
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from a URL by creating a temporary anchor element
|
||||
* @param url - The URL of the file to download (must be a valid URL string)
|
||||
* @param filename - Optional filename override (will use URL filename or default if not provided)
|
||||
* @throws {Error} If the URL is invalid or empty
|
||||
*/
|
||||
export function downloadFile(url: string, filename?: string): void {
|
||||
export const downloadFile = (url: string, filename?: string): void => {
|
||||
if (!url || typeof url !== 'string' || url.trim().length === 0) {
|
||||
throw new Error('Invalid URL provided for download')
|
||||
}
|
||||
|
||||
const inferredFilename =
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download =
|
||||
filename || extractFilenameFromUrl(url) || DEFAULT_DOWNLOAD_FILENAME
|
||||
|
||||
if (isCloud) {
|
||||
// Assets from cross-origin (e.g., GCS) cannot be downloaded this way
|
||||
void downloadViaBlobFetch(url, inferredFilename).catch((error) => {
|
||||
console.error('Failed to download file', error)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
triggerLinkDownload(url, inferredFilename)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a Blob by creating a temporary object URL and anchor element
|
||||
* @param filename - The filename to suggest to the browser
|
||||
* @param blob - The Blob to download
|
||||
*/
|
||||
export function downloadBlob(filename: string, blob: Blob): void {
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
triggerLinkDownload(url, filename)
|
||||
|
||||
// Revoke on the next microtask to give the browser time to start the download
|
||||
queueMicrotask(() => URL.revokeObjectURL(url))
|
||||
// Trigger download
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,15 +39,3 @@ const extractFilenameFromUrl = (url: string): string | null => {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const downloadViaBlobFetch = async (
|
||||
href: string,
|
||||
filename: string
|
||||
): Promise<void> => {
|
||||
const response = await fetch(href)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch ${href}: ${response.status}`)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
downloadBlob(filename, blob)
|
||||
}
|
||||
|
||||
@@ -74,9 +74,7 @@ const activeSidebarTabId = computed(
|
||||
)
|
||||
|
||||
const sidebarStateKey = computed(() => {
|
||||
return unifiedWidth.value
|
||||
? 'unified-sidebar'
|
||||
: (activeSidebarTabId.value ?? '')
|
||||
return unifiedWidth.value ? 'unified-sidebar' : activeSidebarTabId.value ?? ''
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
export default isCloud && window.__CONFIG__?.subscription_required
|
||||
export default isCloud && __BUILD_FLAGS__.REQUIRE_SUBSCRIPTION
|
||||
? defineAsyncComponent(() => import('./CloudRunButtonWrapper.vue'))
|
||||
: defineAsyncComponent(() => import('./ComfyQueueButton.vue'))
|
||||
|
||||
@@ -54,7 +54,7 @@ const {
|
||||
}>()
|
||||
|
||||
const topStyle = computed(() => {
|
||||
const baseClasses = 'relative p-0'
|
||||
const baseClasses = 'relative p-0 overflow-hidden'
|
||||
|
||||
const ratioClasses = {
|
||||
square: 'aspect-square',
|
||||
|
||||
@@ -27,6 +27,28 @@
|
||||
|
||||
<PasswordFields />
|
||||
|
||||
<!-- Personal Data Consent Checkbox -->
|
||||
<FormField
|
||||
v-slot="$field"
|
||||
name="personalDataConsent"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<Checkbox
|
||||
input-id="comfy-org-sign-up-personal-data-consent"
|
||||
:binary="true"
|
||||
:invalid="$field.invalid"
|
||||
/>
|
||||
<label
|
||||
for="comfy-org-sign-up-personal-data-consent"
|
||||
class="text-base font-medium opacity-80"
|
||||
>
|
||||
{{ t('auth.signup.personalDataConsentLabel') }}
|
||||
</label>
|
||||
<small v-if="$field.error" class="-mt-4 text-red-500">{{
|
||||
$field.error.message
|
||||
}}</small>
|
||||
</FormField>
|
||||
|
||||
<!-- Auth Error Message -->
|
||||
<Message v-if="authError" severity="error">
|
||||
{{ authError }}
|
||||
@@ -46,6 +68,7 @@ import type { FormSubmitEvent } from '@primevue/forms'
|
||||
import { Form, FormField } from '@primevue/forms'
|
||||
import { zodResolver } from '@primevue/forms/resolvers/zod'
|
||||
import Button from 'primevue/button'
|
||||
import Checkbox from 'primevue/checkbox'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -2,22 +2,7 @@
|
||||
<!-- Load splitter overlay only after comfyApp is ready. -->
|
||||
<!-- If load immediately, the top-level splitter stateKey won't be correctly
|
||||
synced with the stateStorage (localStorage). -->
|
||||
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady">
|
||||
<template v-if="showUI && workflowTabsPosition === 'Topbar'" #workflow-tabs>
|
||||
<div
|
||||
class="workflow-tabs-container pointer-events-auto relative h-9.5 w-full"
|
||||
>
|
||||
<!-- Native drag area for Electron -->
|
||||
<div
|
||||
v-if="isNativeWindow() && workflowTabsPosition !== 'Topbar'"
|
||||
class="app-drag fixed top-0 left-0 z-10 h-[var(--comfy-topbar-height)] w-full"
|
||||
/>
|
||||
<div class="flex h-full items-center">
|
||||
<WorkflowTabs />
|
||||
<TopbarBadges />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady && betaMenuEnabled">
|
||||
<template v-if="!workspaceStore.focusMode" #side-bar-panel>
|
||||
<SideToolbar />
|
||||
</template>
|
||||
@@ -38,6 +23,7 @@
|
||||
/>
|
||||
</template>
|
||||
</LiteGraphCanvasSplitterOverlay>
|
||||
<GraphCanvasMenu v-if="!betaMenuEnabled && canvasMenuEnabled" />
|
||||
<canvas
|
||||
id="graph-canvas"
|
||||
ref="canvasRef"
|
||||
@@ -105,8 +91,6 @@ import NodeOptions from '@/components/graph/selectionToolbox/NodeOptions.vue'
|
||||
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
|
||||
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
|
||||
import SecondRowWorkflowTabs from '@/components/topbar/SecondRowWorkflowTabs.vue'
|
||||
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
||||
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useViewportCulling } from '@/composables/graph/useViewportCulling'
|
||||
@@ -145,7 +129,6 @@ import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { isNativeWindow } from '@/utils/envUtil'
|
||||
|
||||
const emit = defineEmits<{
|
||||
ready: []
|
||||
@@ -180,10 +163,6 @@ const selectionToolboxEnabled = computed(() =>
|
||||
|
||||
const minimapEnabled = computed(() => settingStore.get('Comfy.Minimap.Visible'))
|
||||
|
||||
const showUI = computed(
|
||||
() => !workspaceStore.focusMode && betaMenuEnabled.value
|
||||
)
|
||||
|
||||
// Feature flags
|
||||
const { shouldRenderVueNodes } = useVueFeatureFlags()
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ let promptInput = findPromptInput()
|
||||
const previousPromptInput = ref<string | null>(null)
|
||||
|
||||
const getPreviousResponseId = (index: number) =>
|
||||
index > 0 ? (parsedHistory.value[index - 1]?.response_id ?? '') : ''
|
||||
index > 0 ? parsedHistory.value[index - 1]?.response_id ?? '' : ''
|
||||
|
||||
const storePromptInput = () => {
|
||||
promptInput ??= widget?.node.widgets?.find((w) => w.name === 'prompt')
|
||||
|
||||
@@ -106,7 +106,7 @@ const getLabel = (val: string | null | undefined) => {
|
||||
if (val == null) return label ?? ''
|
||||
if (!options) return label ?? ''
|
||||
const found = options.find((o) => o.value === val)
|
||||
return found ? found.name : (label ?? '')
|
||||
return found ? found.name : label ?? ''
|
||||
}
|
||||
|
||||
// Extract complex style logic from template
|
||||
|
||||
@@ -76,7 +76,7 @@ const emit = defineEmits<{
|
||||
(e: 'click', event: MouseEvent): void
|
||||
}>()
|
||||
const overlayValue = computed(() =>
|
||||
typeof iconBadge === 'function' ? (iconBadge() ?? '') : iconBadge
|
||||
typeof iconBadge === 'function' ? iconBadge() ?? '' : iconBadge
|
||||
)
|
||||
const shouldShowBadge = computed(() => !!overlayValue.value)
|
||||
const computedTooltip = computed(() => t(tooltip) + tooltipSuffix)
|
||||
|
||||
33
src/components/sidebar/tabs/AssetSidebarTemplate.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex h-full flex-col bg-interface-panel-surface"
|
||||
:class="props.class"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
v-if="slots.top"
|
||||
class="flex min-h-12 items-center border-b border-interface-stroke px-4 py-2"
|
||||
>
|
||||
<slot name="top" />
|
||||
</div>
|
||||
<div v-if="slots.header" class="px-4">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- h-0 to force scrollpanel to grow -->
|
||||
<ScrollPanel class="h-0 grow">
|
||||
<slot name="body" />
|
||||
</ScrollPanel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ScrollPanel from 'primevue/scrollpanel'
|
||||
import { useSlots } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: string
|
||||
}>()
|
||||
|
||||
const slots = useSlots()
|
||||
</script>
|
||||
271
src/components/sidebar/tabs/AssetsSidebarTab.vue
Normal file
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<AssetsSidebarTemplate>
|
||||
<template #top>
|
||||
<span v-if="!isInFolderView" class="font-bold">
|
||||
{{ $t('sideToolbar.mediaAssets') }}
|
||||
</span>
|
||||
<div v-else class="flex w-full items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-bold">{{ $t('Job ID') }}:</span>
|
||||
<span class="text-sm">{{ folderPromptId?.substring(0, 8) }}</span>
|
||||
<button
|
||||
class="m-0 cursor-pointer border-0 bg-transparent p-0 outline-0"
|
||||
role="button"
|
||||
@click="copyJobId"
|
||||
>
|
||||
<i class="mb-1 icon-[lucide--copy] text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ formattedExecutionTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #header>
|
||||
<!-- Job Detail View Header -->
|
||||
<div v-if="isInFolderView" class="pt-4 pb-1">
|
||||
<IconTextButton
|
||||
:label="$t('sideToolbar.backToAssets')"
|
||||
type="secondary"
|
||||
@click="exitFolderView"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--arrow-left] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</div>
|
||||
<!-- Normal Tab View -->
|
||||
<TabList v-else v-model="activeTab" class="pt-4 pb-1">
|
||||
<Tab value="input">{{ $t('sideToolbar.labels.imported') }}</Tab>
|
||||
<Tab value="output">{{ $t('sideToolbar.labels.generated') }}</Tab>
|
||||
</TabList>
|
||||
</template>
|
||||
<template #body>
|
||||
<VirtualGrid
|
||||
v-if="displayAssets.length"
|
||||
:items="mediaAssetsWithKey"
|
||||
:grid-style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
|
||||
padding: '0.5rem',
|
||||
gap: '0.5rem'
|
||||
}"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<MediaAssetCard
|
||||
:asset="item"
|
||||
:selected="selectedAsset?.id === item.id"
|
||||
:show-output-count="
|
||||
activeTab === 'output' &&
|
||||
!isInFolderView &&
|
||||
(item.user_metadata?.outputCount as number) > 1
|
||||
"
|
||||
:output-count="(item.user_metadata?.outputCount as number) || 0"
|
||||
@click="handleAssetSelect(item)"
|
||||
@zoom="handleZoomClick(item)"
|
||||
@output-count-click="enterFolderView(item)"
|
||||
@asset-deleted="refreshAssets"
|
||||
/>
|
||||
</template>
|
||||
</VirtualGrid>
|
||||
<div v-else-if="loading">
|
||||
<ProgressSpinner
|
||||
style="width: 50px; left: 50%; transform: translateX(-50%)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<NoResultsPlaceholder
|
||||
icon="pi pi-info-circle"
|
||||
:title="
|
||||
$t(
|
||||
activeTab === 'input'
|
||||
? 'sideToolbar.noImportedFiles'
|
||||
: 'sideToolbar.noGeneratedFiles'
|
||||
)
|
||||
"
|
||||
:message="$t('sideToolbar.noFilesFoundMessage')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</AssetsSidebarTemplate>
|
||||
<ResultGallery
|
||||
v-model:active-index="galleryActiveIndex"
|
||||
:all-gallery-items="galleryItems"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import VirtualGrid from '@/components/common/VirtualGrid.vue'
|
||||
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
|
||||
import Tab from '@/components/tab/Tab.vue'
|
||||
import TabList from '@/components/tab/TabList.vue'
|
||||
import MediaAssetCard from '@/platform/assets/components/MediaAssetCard.vue'
|
||||
import { useMediaAssets } from '@/platform/assets/composables/useMediaAssets'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { ResultItemImpl } from '@/stores/queueStore'
|
||||
import {
|
||||
formatDuration,
|
||||
getMediaTypeFromFilenamePlural
|
||||
} from '@/utils/formatUtil'
|
||||
|
||||
import AssetsSidebarTemplate from './AssetSidebarTemplate.vue'
|
||||
|
||||
const activeTab = ref<'input' | 'output'>('input')
|
||||
const mediaAssets = ref<AssetItem[]>([])
|
||||
const selectedAsset = ref<AssetItem | null>(null)
|
||||
const folderPromptId = ref<string | null>(null)
|
||||
const folderExecutionTime = ref<number | undefined>(undefined)
|
||||
const isInFolderView = computed(() => folderPromptId.value !== null)
|
||||
|
||||
const formattedExecutionTime = computed(() => {
|
||||
if (!folderExecutionTime.value) return ''
|
||||
return formatDuration(folderExecutionTime.value * 1000)
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
// Use unified media assets implementation that handles cloud/internal automatically
|
||||
const { loading, error, fetchMediaList } = useMediaAssets()
|
||||
|
||||
const galleryActiveIndex = ref(-1)
|
||||
const galleryItems = computed(() => {
|
||||
// Convert AssetItems to ResultItemImpl format for gallery
|
||||
// Use displayAssets instead of mediaAssets to show correct items based on view mode
|
||||
return displayAssets.value.map((asset) => {
|
||||
const resultItem = new ResultItemImpl({
|
||||
filename: asset.name,
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
nodeId: '0',
|
||||
mediaType: getMediaTypeFromFilenamePlural(asset.name)
|
||||
})
|
||||
|
||||
// Override the url getter to use asset.preview_url
|
||||
Object.defineProperty(resultItem, 'url', {
|
||||
get() {
|
||||
return asset.preview_url || ''
|
||||
},
|
||||
configurable: true
|
||||
})
|
||||
|
||||
return resultItem
|
||||
})
|
||||
})
|
||||
|
||||
// Store folder view assets separately
|
||||
const folderAssets = ref<AssetItem[]>([])
|
||||
|
||||
// Get display assets based on view mode
|
||||
const displayAssets = computed(() => {
|
||||
if (isInFolderView.value) {
|
||||
// Show all assets from the folder view
|
||||
return folderAssets.value
|
||||
}
|
||||
|
||||
// Normal view: show grouped assets (already have outputCount from API)
|
||||
return mediaAssets.value
|
||||
})
|
||||
|
||||
// Add key property for VirtualGrid
|
||||
const mediaAssetsWithKey = computed(() => {
|
||||
return displayAssets.value.map((asset) => ({
|
||||
...asset,
|
||||
key: asset.id
|
||||
}))
|
||||
})
|
||||
|
||||
const refreshAssets = async () => {
|
||||
const files = await fetchMediaList(activeTab.value)
|
||||
mediaAssets.value = files
|
||||
selectedAsset.value = null // Clear selection after refresh
|
||||
if (error.value) {
|
||||
console.error('Failed to refresh assets:', error.value)
|
||||
}
|
||||
}
|
||||
|
||||
watch(activeTab, () => {
|
||||
void refreshAssets()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void refreshAssets()
|
||||
})
|
||||
|
||||
const handleAssetSelect = (asset: AssetItem) => {
|
||||
// Toggle selection
|
||||
if (selectedAsset.value?.id === asset.id) {
|
||||
selectedAsset.value = null
|
||||
} else {
|
||||
selectedAsset.value = asset
|
||||
}
|
||||
}
|
||||
|
||||
const handleZoomClick = (asset: AssetItem) => {
|
||||
// Find the index of the clicked asset
|
||||
const index = displayAssets.value.findIndex((a) => a.id === asset.id)
|
||||
if (index !== -1) {
|
||||
galleryActiveIndex.value = index
|
||||
}
|
||||
}
|
||||
|
||||
const enterFolderView = (asset: AssetItem) => {
|
||||
const promptId = asset.user_metadata?.promptId as string
|
||||
const allOutputs = asset.user_metadata?.allOutputs as any[]
|
||||
|
||||
if (promptId && allOutputs) {
|
||||
folderPromptId.value = promptId
|
||||
folderExecutionTime.value = asset.user_metadata
|
||||
?.executionTimeInSeconds as number
|
||||
|
||||
// Convert all outputs to AssetItem format for folder view
|
||||
folderAssets.value = allOutputs.map((output) => ({
|
||||
id: `${promptId}-${output.nodeId}-${output.filename}`,
|
||||
name: output.filename,
|
||||
size: 0,
|
||||
created_at: asset.created_at, // Use parent asset's created_at
|
||||
tags: ['output'],
|
||||
preview_url: output.url,
|
||||
user_metadata: {
|
||||
promptId,
|
||||
nodeId: output.nodeId,
|
||||
subfolder: output.subfolder,
|
||||
executionTimeInSeconds: asset.user_metadata?.executionTimeInSeconds,
|
||||
workflow: asset.user_metadata?.workflow
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const exitFolderView = () => {
|
||||
folderPromptId.value = null
|
||||
folderExecutionTime.value = undefined
|
||||
folderAssets.value = []
|
||||
}
|
||||
|
||||
const copyJobId = async () => {
|
||||
if (folderPromptId.value) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(folderPromptId.value)
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Copied',
|
||||
detail: 'Job ID copied to clipboard',
|
||||
life: 2000
|
||||
})
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to copy Job ID',
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -100,9 +100,9 @@ const coverResult = flatOutputs.length
|
||||
// Using `==` instead of `===` because NodeId can be a string or a number
|
||||
const node: ComfyNode | null =
|
||||
flatOutputs.length && props.task.workflow
|
||||
? (props.task.workflow.nodes.find(
|
||||
? props.task.workflow.nodes.find(
|
||||
(n: ComfyNode) => n.id == coverResult?.nodeId
|
||||
) ?? null)
|
||||
) ?? null
|
||||
: null
|
||||
const progressPreviewBlobUrl = ref('')
|
||||
|
||||
|
||||
43
src/components/tab/Tab.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<button
|
||||
:class="tabClasses"
|
||||
role="tab"
|
||||
:aria-selected="isActive"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const { value } = defineProps<{
|
||||
value: string
|
||||
}>()
|
||||
|
||||
const currentValue = inject<Ref<string>>('tabs-value')
|
||||
const updateValue = inject<(value: string) => void>('tabs-update')
|
||||
|
||||
const isActive = computed(() => currentValue?.value === value)
|
||||
|
||||
const tabClasses = computed(() => {
|
||||
return cn(
|
||||
// Base styles from TextButton
|
||||
'flex items-center justify-center shrink-0',
|
||||
'px-2.5 py-2 text-sm rounded-lg cursor-pointer transition-all duration-200',
|
||||
'outline-hidden border-none',
|
||||
// State styles with semantic tokens
|
||||
isActive.value
|
||||
? 'bg-interface-menu-component-surface-hovered text-text-primary text-bold'
|
||||
: 'bg-transparent text-text-secondary hover:bg-button-hover-surface focus:bg-button-hover-surface'
|
||||
)
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
updateValue?.(value)
|
||||
}
|
||||
</script>
|
||||
153
src/components/tab/TabList.stories.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Tab from './Tab.vue'
|
||||
import TabList from './TabList.vue'
|
||||
|
||||
const meta: Meta<typeof TabList> = {
|
||||
title: 'Components/Tab/TabList',
|
||||
component: TabList,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
modelValue: {
|
||||
control: 'text',
|
||||
description: 'The currently selected tab value'
|
||||
},
|
||||
'onUpdate:modelValue': { action: 'update:modelValue' }
|
||||
}
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => ({
|
||||
components: { TabList, Tab },
|
||||
setup() {
|
||||
const activeTab = ref(args.modelValue || 'tab1')
|
||||
return { activeTab }
|
||||
},
|
||||
template: `
|
||||
<TabList v-model="activeTab">
|
||||
<Tab value="tab1">Tab 1</Tab>
|
||||
<Tab value="tab2">Tab 2</Tab>
|
||||
<Tab value="tab3">Tab 3</Tab>
|
||||
</TabList>
|
||||
<div class="mt-4 p-4 border rounded">
|
||||
Selected tab: {{ activeTab }}
|
||||
</div>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
modelValue: 'tab1'
|
||||
}
|
||||
}
|
||||
|
||||
export const ManyTabs: Story = {
|
||||
render: () => ({
|
||||
components: { TabList, Tab },
|
||||
setup() {
|
||||
const activeTab = ref('tab1')
|
||||
return { activeTab }
|
||||
},
|
||||
template: `
|
||||
<TabList v-model="activeTab">
|
||||
<Tab value="tab1">Dashboard</Tab>
|
||||
<Tab value="tab2">Analytics</Tab>
|
||||
<Tab value="tab3">Reports</Tab>
|
||||
<Tab value="tab4">Settings</Tab>
|
||||
<Tab value="tab5">Profile</Tab>
|
||||
</TabList>
|
||||
<div class="mt-4 p-4 border rounded">
|
||||
Selected tab: {{ activeTab }}
|
||||
</div>
|
||||
`
|
||||
})
|
||||
}
|
||||
|
||||
export const WithIcons: Story = {
|
||||
render: () => ({
|
||||
components: { TabList, Tab },
|
||||
setup() {
|
||||
const activeTab = ref('home')
|
||||
return { activeTab }
|
||||
},
|
||||
template: `
|
||||
<TabList v-model="activeTab">
|
||||
<Tab value="home">
|
||||
<i class="pi pi-home mr-2"></i>
|
||||
Home
|
||||
</Tab>
|
||||
<Tab value="users">
|
||||
<i class="pi pi-users mr-2"></i>
|
||||
Users
|
||||
</Tab>
|
||||
<Tab value="settings">
|
||||
<i class="pi pi-cog mr-2"></i>
|
||||
Settings
|
||||
</Tab>
|
||||
</TabList>
|
||||
<div class="mt-4 p-4 border rounded">
|
||||
Selected tab: {{ activeTab }}
|
||||
</div>
|
||||
`
|
||||
})
|
||||
}
|
||||
|
||||
export const LongLabels: Story = {
|
||||
render: () => ({
|
||||
components: { TabList, Tab },
|
||||
setup() {
|
||||
const activeTab = ref('overview')
|
||||
return { activeTab }
|
||||
},
|
||||
template: `
|
||||
<TabList v-model="activeTab">
|
||||
<Tab value="overview">Project Overview</Tab>
|
||||
<Tab value="documentation">Documentation & Guides</Tab>
|
||||
<Tab value="deployment">Deployment Settings</Tab>
|
||||
<Tab value="monitoring">Monitoring & Analytics</Tab>
|
||||
</TabList>
|
||||
<div class="mt-4 p-4 border rounded">
|
||||
Selected tab: {{ activeTab }}
|
||||
</div>
|
||||
`
|
||||
})
|
||||
}
|
||||
|
||||
export const Interactive: Story = {
|
||||
render: () => ({
|
||||
components: { TabList, Tab },
|
||||
setup() {
|
||||
const activeTab = ref('input')
|
||||
const handleTabChange = (value: string) => {
|
||||
console.log('Tab changed to:', value)
|
||||
}
|
||||
return { activeTab, handleTabChange }
|
||||
},
|
||||
template: `
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold mb-2">Example: Media Assets</h3>
|
||||
<TabList v-model="activeTab" @update:model-value="handleTabChange">
|
||||
<Tab value="input">Imported</Tab>
|
||||
<Tab value="output">Generated</Tab>
|
||||
</TabList>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800 rounded">
|
||||
<div v-if="activeTab === 'input'">
|
||||
<p>Showing imported assets...</p>
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'output'">
|
||||
<p>Showing generated assets...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-600">
|
||||
Current tab value: <code>{{ activeTab }}</code>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
}
|
||||
23
src/components/tab/TabList.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="flex items-center gap-2 pb-1">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { provide, toRef } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
// Provide for child Tab components
|
||||
provide('tabs-value', toRef(props, 'modelValue'))
|
||||
provide('tabs-update', (value: string) => emit('update:modelValue', value))
|
||||
</script>
|
||||
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<TopbarBadge
|
||||
:badge="cloudBadge"
|
||||
:display-mode="displayMode"
|
||||
:reverse-order="reverseOrder"
|
||||
:no-padding="noPadding"
|
||||
:background-color="backgroundColor"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
import type { TopbarBadge as TopbarBadgeType } from '@/types/comfy'
|
||||
|
||||
import TopbarBadge from './TopbarBadge.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
displayMode?: 'full' | 'compact' | 'icon-only'
|
||||
reverseOrder?: boolean
|
||||
noPadding?: boolean
|
||||
backgroundColor?: string
|
||||
}>(),
|
||||
{
|
||||
displayMode: 'full',
|
||||
reverseOrder: false,
|
||||
noPadding: false,
|
||||
backgroundColor: 'var(--comfy-menu-secondary-bg)'
|
||||
}
|
||||
)
|
||||
|
||||
const cloudBadge = computed<TopbarBadgeType>(() => ({
|
||||
label: t('g.beta'),
|
||||
text: 'Comfy Cloud'
|
||||
}))
|
||||
</script>
|
||||
@@ -69,22 +69,6 @@ vi.mock('@/services/dialogService', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
// Mock the firebaseAuthStore
|
||||
vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
useFirebaseAuthStore: vi.fn(() => ({
|
||||
getAuthHeader: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ Authorization: 'Bearer mock-token' })
|
||||
}))
|
||||
}))
|
||||
|
||||
// Mock the useSubscription composable
|
||||
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
|
||||
useSubscription: vi.fn(() => ({
|
||||
isActiveSubscription: vi.fn().mockReturnValue(true)
|
||||
}))
|
||||
}))
|
||||
|
||||
// Mock UserAvatar component
|
||||
vi.mock('@/components/common/UserAvatar.vue', () => ({
|
||||
default: {
|
||||
|
||||
@@ -67,11 +67,7 @@
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<UserCredit text-class="text-2xl" />
|
||||
<Button
|
||||
v-if="isActiveSubscription"
|
||||
:label="$t('credits.topUp.topUp')"
|
||||
@click="handleTopUp"
|
||||
/>
|
||||
<Button :label="$t('credits.topUp.topUp')" @click="handleTopUp" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +82,6 @@ import UserAvatar from '@/components/common/UserAvatar.vue'
|
||||
import UserCredit from '@/components/common/UserCredit.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -97,7 +92,6 @@ const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
|
||||
useCurrentUser()
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const dialogService = useDialogService()
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
|
||||
const handleOpenUserSettings = () => {
|
||||
dialogService.showSettingsDialog('user')
|
||||
|
||||