mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 08:00:05 +00:00
This commit integrates the previously recovered ComfyUI Manager functionality with significant enhancements from PR #3367, including: ## Core Manager System Recovery - **v2 API Integration**: All manager endpoints now use `/v2/manager/queue/*` - **Task Queue System**: Complete client-side task queuing with WebSocket status - **Service Layer**: Comprehensive manager service with all CRUD operations - **Store Integration**: Full manager store with progress dialog support ## New Features & Enhancements - **Reactive Feature Flags**: Foundation for dynamic feature toggling - **Enhanced UI Components**: Improved loading states, progress tracking - **Package Management**: Install, update, enable/disable functionality - **Version Selection**: Support for latest/nightly package versions - **Progress Dialogs**: Real-time installation progress with logs - **Missing Node Detection**: Automated detection and installation prompts ## Technical Improvements - **TypeScript Definitions**: Complete type system for manager operations - **WebSocket Integration**: Real-time status updates via `cm-queue-status` - **Error Handling**: Comprehensive error handling with user feedback - **Testing**: Updated test suites for new functionality - **Documentation**: Complete backup documentation for recovery process ## API Endpoints Restored - `manager/queue/start` - Start task queue - `manager/queue/status` - Get queue status - `manager/queue/task` - Queue individual tasks - `manager/queue/install` - Install packages - `manager/queue/update` - Update packages - `manager/queue/disable` - Disable packages ## Breaking Changes - Manager API base URL changed to `/v2/` - Updated TypeScript interfaces for manager operations - New WebSocket message format for queue status This restores all critical manager functionality lost during the previous rebase while integrating the latest enhancements and maintaining compatibility with the current main branch. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
250 lines
8.6 KiB
TypeScript
250 lines
8.6 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
|
|
import { comfyPageFixture } from '../fixtures/ComfyPage'
|
|
|
|
const test = comfyPageFixture
|
|
|
|
const BLUE_COLOR = 'rgb(51, 51, 85)'
|
|
const RED_COLOR = 'rgb(85, 51, 51)'
|
|
|
|
test.describe('Selection Toolbox', () => {
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
|
})
|
|
|
|
test('shows selection toolbox', async ({ comfyPage }) => {
|
|
// By default, selection toolbox should be enabled
|
|
await expect(comfyPage.selectionToolbox).not.toBeVisible()
|
|
|
|
// Select multiple nodes
|
|
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])
|
|
|
|
// Selection toolbox should be visible with multiple nodes selected
|
|
await expect(comfyPage.selectionToolbox).toBeVisible()
|
|
// Border is now drawn on canvas, check via screenshot
|
|
await expect(comfyPage.canvas).toHaveScreenshot(
|
|
'selection-toolbox-multiple-nodes-border.png'
|
|
)
|
|
})
|
|
|
|
test('shows at correct position when node is pasted', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
await comfyPage.ctrlC()
|
|
await comfyPage.page.mouse.move(100, 100)
|
|
await comfyPage.ctrlV()
|
|
|
|
const toolboxContainer = comfyPage.selectionToolbox
|
|
await expect(toolboxContainer).toBeVisible()
|
|
|
|
// Verify toolbox is positioned (canvas-based positioning has different coordinates)
|
|
const boundingBox = await toolboxContainer.boundingBox()
|
|
expect(boundingBox).not.toBeNull()
|
|
// Canvas-based positioning can vary, just verify toolbox appears in reasonable bounds
|
|
expect(boundingBox!.x).toBeGreaterThan(-200) // Not too far off-screen left
|
|
expect(boundingBox!.x).toBeLessThan(1000) // Not too far off-screen right
|
|
expect(boundingBox!.y).toBeGreaterThan(-100) // Not too far off-screen top
|
|
})
|
|
|
|
test('hide when select and drag happen at the same time', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
|
const node = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
|
const nodePos = await node.getPosition()
|
|
|
|
// Drag on the title of the node
|
|
await comfyPage.page.mouse.move(nodePos.x + 100, nodePos.y - 15)
|
|
await comfyPage.page.mouse.down()
|
|
await comfyPage.page.mouse.move(nodePos.x + 200, nodePos.y + 200)
|
|
await comfyPage.nextFrame()
|
|
await expect(comfyPage.selectionToolbox).not.toBeVisible()
|
|
})
|
|
|
|
test('shows border only with multiple selections', async ({ comfyPage }) => {
|
|
// Select single node
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
|
|
// Selection toolbox should be visible but without border
|
|
await expect(comfyPage.selectionToolbox).toBeVisible()
|
|
// Border is now drawn on canvas, check via screenshot
|
|
await expect(comfyPage.canvas).toHaveScreenshot(
|
|
'selection-toolbox-single-node-no-border.png'
|
|
)
|
|
|
|
// Select multiple nodes
|
|
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])
|
|
|
|
// Selection border should show with multiple selections (canvas-based)
|
|
await expect(comfyPage.canvas).toHaveScreenshot(
|
|
'selection-toolbox-multiple-selections-border.png'
|
|
)
|
|
|
|
// Deselect to single node
|
|
await comfyPage.selectNodes(['CLIP Text Encode (Prompt)'])
|
|
|
|
// Border should be hidden again (canvas-based)
|
|
await expect(comfyPage.canvas).toHaveScreenshot(
|
|
'selection-toolbox-single-selection-no-border.png'
|
|
)
|
|
})
|
|
|
|
test('displays bypass button in toolbox when nodes are selected', async ({
|
|
comfyPage
|
|
}) => {
|
|
// A group + a KSampler node
|
|
await comfyPage.loadWorkflow('groups/single_group')
|
|
|
|
// Select group + node should show bypass button
|
|
await comfyPage.page.focus('canvas')
|
|
await comfyPage.page.keyboard.press('Control+A')
|
|
await expect(
|
|
comfyPage.page.locator(
|
|
'.selection-toolbox *[data-testid="bypass-button"]'
|
|
)
|
|
).toBeVisible()
|
|
|
|
// Deselect node (Only group is selected) should hide bypass button
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
await expect(
|
|
comfyPage.page.locator(
|
|
'.selection-toolbox *[data-testid="bypass-button"]'
|
|
)
|
|
).not.toBeVisible()
|
|
})
|
|
|
|
test.describe('Color Picker', () => {
|
|
test('displays color picker button and allows color selection', async ({
|
|
comfyPage
|
|
}) => {
|
|
// Select a node
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
|
|
// Color picker button should be visible
|
|
const colorPickerButton = comfyPage.page.locator(
|
|
'.selection-toolbox .pi-circle-fill'
|
|
)
|
|
await expect(colorPickerButton).toBeVisible()
|
|
|
|
// Click color picker button
|
|
await colorPickerButton.click()
|
|
|
|
// Color picker dropdown should be visible
|
|
const colorPickerDropdown = comfyPage.page.locator(
|
|
'.color-picker-container'
|
|
)
|
|
await expect(colorPickerDropdown).toBeVisible()
|
|
|
|
// Select a color (e.g., blue)
|
|
const blueColorOption = colorPickerDropdown.locator(
|
|
'i[data-testid="blue"]'
|
|
)
|
|
await blueColorOption.click()
|
|
|
|
// Dropdown should close after selection
|
|
await expect(colorPickerDropdown).not.toBeVisible()
|
|
|
|
// Node should have the selected color class/style
|
|
// Note: Exact verification method depends on how color is applied to nodes
|
|
const selectedNode = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
|
expect(selectedNode.getProperty('color')).not.toBeNull()
|
|
})
|
|
|
|
test('color picker shows current color of selected nodes', async ({
|
|
comfyPage
|
|
}) => {
|
|
// Select multiple nodes
|
|
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])
|
|
|
|
const colorPickerButton = comfyPage.page.locator(
|
|
'.selection-toolbox .pi-circle-fill'
|
|
)
|
|
|
|
// Initially should show default color
|
|
await expect(colorPickerButton).not.toHaveAttribute('color')
|
|
|
|
// Click color picker and select a color
|
|
await colorPickerButton.click()
|
|
const redColorOption = comfyPage.page.locator(
|
|
'.color-picker-container i[data-testid="red"]'
|
|
)
|
|
await redColorOption.click()
|
|
|
|
// Button should now show the selected color
|
|
await expect(colorPickerButton).toHaveCSS('color', RED_COLOR)
|
|
})
|
|
|
|
test('color picker shows mixed state for differently colored selections', async ({
|
|
comfyPage
|
|
}) => {
|
|
// Select first node and color it
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
await comfyPage.page.locator('.selection-toolbox .pi-circle-fill').click()
|
|
await comfyPage.page
|
|
.locator('.color-picker-container i[data-testid="blue"]')
|
|
.click()
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
|
|
// Select second node and color it differently
|
|
await comfyPage.selectNodes(['CLIP Text Encode (Prompt)'])
|
|
await comfyPage.page.locator('.selection-toolbox .pi-circle-fill').click()
|
|
await comfyPage.page
|
|
.locator('.color-picker-container i[data-testid="red"]')
|
|
.click()
|
|
|
|
// Select both nodes
|
|
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])
|
|
|
|
// Color picker should show null/mixed state
|
|
const colorPickerButton = comfyPage.page.locator(
|
|
'.selection-toolbox .pi-circle-fill'
|
|
)
|
|
await expect(colorPickerButton).not.toHaveAttribute('color')
|
|
})
|
|
|
|
test('color picker shows correct color when selecting pre-colored node', async ({
|
|
comfyPage
|
|
}) => {
|
|
// First color a node
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
await comfyPage.page.locator('.selection-toolbox .pi-circle-fill').click()
|
|
await comfyPage.page
|
|
.locator('.color-picker-container i[data-testid="blue"]')
|
|
.click()
|
|
|
|
// Clear selection
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
|
|
// Re-select the node
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
|
|
// Color picker button should show the correct color
|
|
const colorPickerButton = comfyPage.page.locator(
|
|
'.selection-toolbox .pi-circle-fill'
|
|
)
|
|
await expect(colorPickerButton).toHaveCSS('color', BLUE_COLOR)
|
|
})
|
|
|
|
test('colorization via color picker can be undone', async ({
|
|
comfyPage
|
|
}) => {
|
|
// Select a node and color it
|
|
await comfyPage.selectNodes(['KSampler'])
|
|
await comfyPage.page.locator('.selection-toolbox .pi-circle-fill').click()
|
|
await comfyPage.page
|
|
.locator('.color-picker-container i[data-testid="blue"]')
|
|
.click()
|
|
|
|
// Undo the colorization
|
|
await comfyPage.page.keyboard.press('Control+Z')
|
|
await comfyPage.nextFrame()
|
|
|
|
// Node should be uncolored again
|
|
const selectedNode = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
|
expect(await selectedNode.getProperty('color')).toBeUndefined()
|
|
})
|
|
})
|
|
})
|