Files
ComfyUI_frontend/browser_tests/tests/selectionToolbox.spec.ts
bymyself 380f335bff feat: Integrate ComfyUI Manager migration with v2 API and enhanced UI
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>
2025-08-30 13:44:27 -07:00

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()
})
})
})