mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-27 03:19:56 +00:00
* WIP
* WIP: UI design for right click menu
* feat: add composable for node customization and information handling
* fix: correct v-show directive in MaskEditorButton and enhance MoreOptions functionality
* feat: add selection and subgraph operations composables for enhanced graph management
* fix: update computed properties to use 'void' for non-reactive calls and add MenuOptionItem component
* feat: add composables for More Options menu and submenu positioning logic
* feat: refactor MoreOptions component to use MenuOptionItem for menu rendering and streamline submenu handling
* feat: implement SubmenuPopover component for enhanced submenu functionality and selection handling
* feat: add 'More Options' label and enhance shape options in localization file
* refactor: simplify shape name handling by removing Pascal case conversion and using localized names
* refactor: enhance submenu handling by dynamically setting refs and improving key assignment
* feat: implement useNodeArrangement composable for node alignment and distribution functionality
* feat: enhance useMoreOptionsMenu with image node operations and alignment options
* feat: localize context menu options and enhance submenu handling
* refactor: improve type safety for title assignment in selection operations and enhance color option retrieval in node customization
* fix: adjust component order in SelectionToolbox for improved layout
* feat: update FrameNodes button visibility and tooltip, and add localization for frameNodes
* feat: enhance button visibility logic in SelectionToolbox based on selection types
* refactor: reorganize properties panel option in More Options menu for single nodes
* remove excessive logging and alerts
* fix component tests
* ad browser tests
* feat: enhance popover behavior in MoreOptions component to manage visibility state during selection overlay changes
* refactor: update visibility logic for buttons in SelectionToolbox and ExecuteButton components
* refactor: remove duplicate shape option and clean up shapeOptions array
* refactor: update help toggle logic in InfoButton and useMoreOptionsMenu to manage sidebar and help state
* refactor: streamline node info handling and integrate output node filtering in useNodeInfo and useMoreOptionsMenu
* Added useSelectionState composable consolidating all selection-derived state and the node help toggle
* Updated toolbox buttons (InfoButton, BookmarkButton, BypassButton, MaskEditorButton, ConvertToSubgraphButton, PinButton, DeleteButton, ColorPickerButton, ExecuteButton, FrameNodes, Load3DViewerButton) to remove duplicated selection logic and use useSelectionState
* Introduced HideReason ('manual' | 'drag') to differentiate drag-induced hides from manual/outside hides in MoreOptions
* refactor: enhance popover visibility handling during drag events using canvas state
* fix: update shape option name from 'default' to 'box' and add localization for 'box'
* refactor: streamline BypassButton logic and enhance MoreOptions menu with state bumping
* refactor: remove toast notifications from subgraph operations for cleaner logic
* refactor: ensure menu options re-compute when selection flags change
* feat: Enhance MoreOptions behavior with drag-and-drop support
* fix: Update mask icon class for consistent styling in MaskEditorButton
* refactor: Standardize icon sizes and classes across selection toolbox buttons
* refactor: Update layout and styling in SelectionToolbox and MoreOptions components
* refactor: Improve selection toolbox behavior with more options state management
* Refactor: Remove unused imports and conditionally add subgraph option in menu
* Enhance popover behavior: add show/hide event handlers and improve positioning logic
* Cleanup: Remove debug comments from popover functions for clarity
* Refactor: Clean up FrameNodes component and add MenuOptionBadge for better option display
* Cleanup: Remove debug comments from useSelectionToolboxPosition for clarity
* Add useFrameNodes composable for grouping selected nodes
* Refactor: Update shape options in useNodeCustomization and localize frame nodes label
* fix tests
* Cleanup: Remove packageManager entry from package.json
* Refactor: Replace ILucide icons with named imports from lucide-vue-next
* Refactor: Update shape selection and improve color picker behavior in selection toolbox
* Update test expectations [skip ci]
* feat: Enhance More Options Menu for group node management and update localization strings
* refactor: Comment out PublishButton
* refactor: Comment out test for bookmark button visibility in SelectionToolbox
* refactor: Update class names for dark theme compatibility in ExecuteButton and MenuOptionItem components
* refactor: Modularize menu options by creating dedicated composables for group, image, node, and selection operations
* refactor: Update selectors in tests to match design changes
* refactor: Update help button selector in Node Help tests
* refactor: Update getGroupColorOptions to accept groupContext and bump parameters
* Update test expectations [skip ci]
* refactor: Center KSampler node before interaction in More Options submenu tests
* refactor: Adjust KSampler node positioning and simplify button click in More Options submenu tests
* refactor: Rename comfyPageFixture import for clarity
* refactor: use gap-1 instead of the explicit gap-[4px]
* refactor: Replace app.canvas with canvasStore.getCanvas for state management
* refactor: Simplify prop access by removing 'props.' prefix in MenuOptionItem component
* refactor: Remove explicit type annotation for item in buildSelectionSignature function
* refactor: Replace Lucide icons with string-based icon references in menu options
* refactor: Remove export from interface declarations for improved clarity
* refactor: Simplify class binding in BypassButton component for improved readability
* refactor: Update button class for consistent sizing in ExecuteButton component
* refactor: Update help button locator class for consistency in Node Help tests
* fix node help test
* refactor: Remove unused imports and simplify visibility conditions in selection toolbox components
* feat: Add 3D node selection logic and cleanup on unmount for selection toolbox
* refactor: Update help button locator to use consistent data-testid in Node Help tests
* fix: Correct help button locator syntax in Node Help tests
* refactor: Change resetMoreOptionsState to an internal function in useSelectionToolboxPosition
* test: Add Load3D node visibility logic for ColorPickerButton and remove redundant test case
* fix: Increase tooltip show delay for ColorPickerButton
* fix: Update selectedOutputNodes computation to filter by isLGraphNode
* fix: Remove unused nodeDef reference from InfoButton and submenu trigger from MenuOptionItem
* fix: Update showInfoButton logic to depend on nodeDef value
* refactor: Remove deprecated getBasicNodeOptions function for cleaner code
* refactor: Replace useNodeInfo with useSelectedNodeActions
* refactor: Integrate useNodeDefStore for improved node definition handling in SelectionToolbox and InfoButton tests
* refactor: Introduce useCanvasRefresh composable for consistent canvas refresh logic across node operations
* refactor: Remove irrelevant append-to attribute from Popover
* refactor: Use storeToRefs for selectedItems in useSelectionState and add tests for selection logic
* refactor: Update ExecuteButton to use hasOutputNodesSelected for visibility and remove unnecessary computed property
* refactor: move display of execution button tests to selectionToolbox
---------
Co-authored-by: github-actions <github-actions@github.com>
554 lines
18 KiB
TypeScript
554 lines
18 KiB
TypeScript
import {
|
||
comfyExpect as expect,
|
||
comfyPageFixture as test
|
||
} from '../fixtures/ComfyPage'
|
||
|
||
// TODO: there might be a better solution for this
|
||
// Helper function to pan canvas and select node
|
||
async function selectNodeWithPan(comfyPage: any, nodeRef: any) {
|
||
const nodePos = await nodeRef.getPosition()
|
||
|
||
await comfyPage.page.evaluate((pos) => {
|
||
const app = window['app']
|
||
const canvas = app.canvas
|
||
canvas.ds.offset[0] = -pos.x + canvas.canvas.width / 2
|
||
canvas.ds.offset[1] = -pos.y + canvas.canvas.height / 2 + 100
|
||
canvas.setDirty(true, true)
|
||
}, nodePos)
|
||
|
||
await comfyPage.nextFrame()
|
||
await nodeRef.click('title')
|
||
}
|
||
|
||
test.describe('Node Help', () => {
|
||
test.beforeEach(async ({ comfyPage }) => {
|
||
await comfyPage.setup()
|
||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||
})
|
||
|
||
test.describe('Selection Toolbox', () => {
|
||
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')
|
||
|
||
// Select a single node (KSampler) using node references
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
if (ksamplerNodes.length === 0) {
|
||
throw new Error('No KSampler nodes found in the workflow')
|
||
}
|
||
|
||
// Select the node with panning to ensure toolbox is visible
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
// Wait for selection toolbox to appear
|
||
await expect(comfyPage.selectionToolbox).toBeVisible()
|
||
|
||
// Click the help button in the selection toolbox
|
||
const helpButton = comfyPage.selectionToolbox.locator(
|
||
'button[data-testid="info-button"]'
|
||
)
|
||
await expect(helpButton).toBeVisible()
|
||
await helpButton.click()
|
||
|
||
// Verify that the node library sidebar is opened
|
||
await expect(
|
||
comfyPage.menu.nodeLibraryTab.selectedTabButton
|
||
).toBeVisible()
|
||
|
||
// Verify that the help page is shown for the correct node
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('KSampler')
|
||
await expect(helpPage.locator('.node-help-content')).toBeVisible()
|
||
})
|
||
})
|
||
|
||
test.describe('Node Library Sidebar', () => {
|
||
test('Should open help menu from node library', async ({ comfyPage }) => {
|
||
// Open the node library sidebar
|
||
await comfyPage.menu.nodeLibraryTab.open()
|
||
|
||
// Wait for node library to load
|
||
await expect(comfyPage.menu.nodeLibraryTab.nodeLibraryTree).toBeVisible()
|
||
|
||
// Search for KSampler to make it easier to find
|
||
await comfyPage.menu.nodeLibraryTab.nodeLibrarySearchBoxInput.fill(
|
||
'KSampler'
|
||
)
|
||
|
||
// Find the KSampler node in search results
|
||
const ksamplerNode = comfyPage.page
|
||
.locator('.tree-explorer-node-label')
|
||
.filter({ hasText: 'KSampler' })
|
||
.first()
|
||
await expect(ksamplerNode).toBeVisible()
|
||
|
||
// Hover over the node to show action buttons
|
||
await ksamplerNode.hover()
|
||
|
||
// Click the help button
|
||
const helpButton = ksamplerNode.locator('button:has(.pi-question)')
|
||
await expect(helpButton).toBeVisible()
|
||
await helpButton.click()
|
||
|
||
// Verify that the help page is shown
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('KSampler')
|
||
await expect(helpPage.locator('.node-help-content')).toBeVisible()
|
||
})
|
||
|
||
test('Should show node library tab when clicking back from help page', async ({
|
||
comfyPage
|
||
}) => {
|
||
// Open the node library sidebar
|
||
await comfyPage.menu.nodeLibraryTab.open()
|
||
|
||
// Wait for node library to load
|
||
await expect(comfyPage.menu.nodeLibraryTab.nodeLibraryTree).toBeVisible()
|
||
|
||
// Search for KSampler
|
||
await comfyPage.menu.nodeLibraryTab.nodeLibrarySearchBoxInput.fill(
|
||
'KSampler'
|
||
)
|
||
|
||
// Find and interact with the node
|
||
const ksamplerNode = comfyPage.page
|
||
.locator('.tree-explorer-node-label')
|
||
.filter({ hasText: 'KSampler' })
|
||
.first()
|
||
await ksamplerNode.hover()
|
||
const helpButton = ksamplerNode.locator('button:has(.pi-question)')
|
||
await helpButton.click()
|
||
|
||
// Verify help page is shown
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('KSampler')
|
||
|
||
// Click the back button - use a more specific selector
|
||
const backButton = comfyPage.page.locator('button:has(.pi-arrow-left)')
|
||
await expect(backButton).toBeVisible()
|
||
await backButton.click()
|
||
|
||
// Verify that we're back to the node library view
|
||
await expect(comfyPage.menu.nodeLibraryTab.nodeLibraryTree).toBeVisible()
|
||
await expect(
|
||
comfyPage.menu.nodeLibraryTab.nodeLibrarySearchBoxInput
|
||
).toBeVisible()
|
||
|
||
// Verify help page is no longer visible
|
||
await expect(helpPage.locator('.node-help-content')).not.toBeVisible()
|
||
})
|
||
})
|
||
|
||
test.describe('Help Content', () => {
|
||
test.beforeEach(async ({ comfyPage }) => {
|
||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||
})
|
||
|
||
test('Should display loading state while fetching help', async ({
|
||
comfyPage
|
||
}) => {
|
||
// Mock slow network response
|
||
await comfyPage.page.route('**/docs/**/*.md', async (route) => {
|
||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: '# Test Help Content\nThis is test help content.'
|
||
})
|
||
})
|
||
|
||
// Load workflow and select a node
|
||
await comfyPage.loadWorkflow('default')
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
// Click help button
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
// Verify loading spinner is shown
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage.locator('.p-progressspinner')).toBeVisible()
|
||
|
||
// Wait for content to load
|
||
await expect(helpPage).toContainText('Test Help Content')
|
||
})
|
||
|
||
test('Should display fallback content when help file not found', async ({
|
||
comfyPage
|
||
}) => {
|
||
// Mock 404 response for help files
|
||
await comfyPage.page.route('**/docs/**/*.md', async (route) => {
|
||
await route.fulfill({
|
||
status: 404,
|
||
body: 'Not Found'
|
||
})
|
||
})
|
||
|
||
// Load workflow and select a node
|
||
await comfyPage.loadWorkflow('default')
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
// Click help button
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
// Verify fallback content is shown (description, inputs, outputs)
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('Description')
|
||
await expect(helpPage).toContainText('Inputs')
|
||
await expect(helpPage).toContainText('Outputs')
|
||
})
|
||
|
||
test('Should render markdown with images correctly', async ({
|
||
comfyPage
|
||
}) => {
|
||
// Mock response with markdown containing images
|
||
await comfyPage.page.route('**/docs/KSampler/en.md', async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: `# KSampler Documentation
|
||
|
||

|
||

|
||
|
||
## Parameters
|
||
- **steps**: Number of steps
|
||
`
|
||
})
|
||
})
|
||
|
||
await comfyPage.loadWorkflow('default')
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('KSampler Documentation')
|
||
|
||
// Check that relative image paths are prefixed correctly
|
||
const relativeImage = helpPage.locator('img[alt="Example Image"]')
|
||
await expect(relativeImage).toBeVisible()
|
||
await expect(relativeImage).toHaveAttribute(
|
||
'src',
|
||
/.*\/docs\/KSampler\/example\.jpg/
|
||
)
|
||
|
||
// Check that absolute URLs are not modified
|
||
const externalImage = helpPage.locator('img[alt="External Image"]')
|
||
await expect(externalImage).toHaveAttribute(
|
||
'src',
|
||
'https://example.com/image.png'
|
||
)
|
||
})
|
||
|
||
test('Should render video elements with source tags in markdown', async ({
|
||
comfyPage
|
||
}) => {
|
||
// Mock response with video elements
|
||
await comfyPage.page.route('**/docs/KSampler/en.md', async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: `# KSampler Demo
|
||
|
||
<video src="demo.mp4" controls autoplay></video>
|
||
<video src="/absolute/video.mp4" controls></video>
|
||
|
||
<video controls>
|
||
<source src="video.mp4" type="video/mp4">
|
||
<source src="https://example.com/video.webm" type="video/webm">
|
||
</video>
|
||
`
|
||
})
|
||
})
|
||
|
||
await comfyPage.loadWorkflow('default')
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
|
||
// Check relative video paths are prefixed
|
||
const relativeVideo = helpPage.locator('video[src*="demo.mp4"]')
|
||
await expect(relativeVideo).toBeVisible()
|
||
await expect(relativeVideo).toHaveAttribute(
|
||
'src',
|
||
/.*\/docs\/KSampler\/demo\.mp4/
|
||
)
|
||
await expect(relativeVideo).toHaveAttribute('controls', '')
|
||
await expect(relativeVideo).toHaveAttribute('autoplay', '')
|
||
|
||
// Check absolute paths are not modified
|
||
const absoluteVideo = helpPage.locator('video[src="/absolute/video.mp4"]')
|
||
await expect(absoluteVideo).toHaveAttribute('src', '/absolute/video.mp4')
|
||
|
||
// Check video source elements
|
||
const relativeVideoSource = helpPage.locator('source[src*="video.mp4"]')
|
||
await expect(relativeVideoSource).toHaveAttribute(
|
||
'src',
|
||
/.*\/docs\/KSampler\/video\.mp4/
|
||
)
|
||
|
||
const externalVideoSource = helpPage.locator(
|
||
'source[src="https://example.com/video.webm"]'
|
||
)
|
||
await expect(externalVideoSource).toHaveAttribute(
|
||
'src',
|
||
'https://example.com/video.webm'
|
||
)
|
||
})
|
||
|
||
test('Should handle custom node documentation paths', async ({
|
||
comfyPage
|
||
}) => {
|
||
// First load workflow with custom node
|
||
await comfyPage.loadWorkflow('groupnodes/group_node_v1.3.3')
|
||
|
||
// Mock custom node documentation with fallback
|
||
await comfyPage.page.route(
|
||
'**/extensions/*/docs/*/en.md',
|
||
async (route) => {
|
||
await route.fulfill({ status: 404 })
|
||
}
|
||
)
|
||
|
||
await comfyPage.page.route('**/extensions/*/docs/*.md', async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: `# Custom Node Documentation
|
||
|
||
This is documentation for a custom node.
|
||
|
||

|
||
`
|
||
})
|
||
})
|
||
|
||
// Find and select a custom/group node
|
||
const nodeRefs = await comfyPage.page.evaluate(() => {
|
||
return window['app'].graph.nodes.map((n: any) => n.id)
|
||
})
|
||
if (nodeRefs.length > 0) {
|
||
const firstNode = await comfyPage.getNodeRefById(nodeRefs[0])
|
||
await selectNodeWithPan(comfyPage, firstNode)
|
||
}
|
||
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
if (await helpButton.isVisible()) {
|
||
await helpButton.click()
|
||
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('Custom Node Documentation')
|
||
|
||
// Check image path for custom nodes
|
||
const image = helpPage.locator('img[alt="Custom Image"]')
|
||
await expect(image).toHaveAttribute(
|
||
'src',
|
||
/.*\/extensions\/.*\/docs\/assets\/custom\.png/
|
||
)
|
||
}
|
||
})
|
||
|
||
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({
|
||
status: 200,
|
||
body: `# Safe Content
|
||
|
||
<script>alert('XSS')</script>
|
||
<img src="x" onerror="alert('XSS')">
|
||
<a href="javascript:alert('XSS')">Dangerous Link</a>
|
||
<iframe src="evil.com"></iframe>
|
||
|
||
<!-- Safe content -->
|
||
<video src="safe.mp4" controls></video>
|
||
<img src="safe.jpg" alt="Safe Image">
|
||
`
|
||
})
|
||
})
|
||
|
||
await comfyPage.loadWorkflow('default')
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
|
||
// Dangerous elements should be removed
|
||
await expect(helpPage.locator('script')).toHaveCount(0)
|
||
await expect(helpPage.locator('iframe')).toHaveCount(0)
|
||
|
||
// Check that onerror attribute is removed
|
||
const images = helpPage.locator('img')
|
||
const imageCount = await images.count()
|
||
for (let i = 0; i < imageCount; i++) {
|
||
const img = images.nth(i)
|
||
const onError = await img.getAttribute('onerror')
|
||
expect(onError).toBeNull()
|
||
}
|
||
|
||
// Check that javascript: links are sanitized
|
||
const links = helpPage.locator('a')
|
||
const linkCount = await links.count()
|
||
for (let i = 0; i < linkCount; i++) {
|
||
const link = links.nth(i)
|
||
const href = await link.getAttribute('href')
|
||
if (href !== null) {
|
||
expect(href).not.toContain('javascript:')
|
||
}
|
||
}
|
||
|
||
// Safe content should remain
|
||
await expect(helpPage.locator('video[src*="safe.mp4"]')).toBeVisible()
|
||
await expect(helpPage.locator('img[alt="Safe Image"]')).toBeVisible()
|
||
})
|
||
|
||
test('Should handle locale-specific documentation', async ({
|
||
comfyPage
|
||
}) => {
|
||
// Mock different responses for different locales
|
||
await comfyPage.page.route('**/docs/KSampler/ja.md', async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: `# KSamplerノード
|
||
|
||
これは日本語のドキュメントです。
|
||
`
|
||
})
|
||
})
|
||
|
||
await comfyPage.page.route('**/docs/KSampler/en.md', async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: `# KSampler Node
|
||
|
||
This is English documentation.
|
||
`
|
||
})
|
||
})
|
||
|
||
// Set locale to Japanese
|
||
await comfyPage.setSetting('Comfy.Locale', 'ja')
|
||
|
||
await comfyPage.loadWorkflow('default')
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('KSamplerノード')
|
||
await expect(helpPage).toContainText('これは日本語のドキュメントです')
|
||
|
||
// Reset locale
|
||
await comfyPage.setSetting('Comfy.Locale', 'en')
|
||
})
|
||
|
||
test('Should handle network errors gracefully', async ({ comfyPage }) => {
|
||
// Mock network error
|
||
await comfyPage.page.route('**/docs/**/*.md', async (route) => {
|
||
await route.abort('failed')
|
||
})
|
||
|
||
await comfyPage.loadWorkflow('default')
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
|
||
// Should show fallback content (node description)
|
||
await expect(helpPage).toBeVisible()
|
||
await expect(helpPage.locator('.p-progressspinner')).not.toBeVisible()
|
||
|
||
// Should show some content even on error
|
||
const content = await helpPage.textContent()
|
||
expect(content).toBeTruthy()
|
||
})
|
||
|
||
test('Should update help content when switching between nodes', async ({
|
||
comfyPage
|
||
}) => {
|
||
// Mock different help content for different nodes
|
||
await comfyPage.page.route('**/docs/KSampler/en.md', async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: '# KSampler Help\n\nThis is KSampler documentation.'
|
||
})
|
||
})
|
||
|
||
await comfyPage.page.route(
|
||
'**/docs/CheckpointLoaderSimple/en.md',
|
||
async (route) => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
body: '# Checkpoint Loader Help\n\nThis is Checkpoint Loader documentation.'
|
||
})
|
||
}
|
||
)
|
||
|
||
await comfyPage.loadWorkflow('default')
|
||
|
||
// Select KSampler first
|
||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||
await selectNodeWithPan(comfyPage, ksamplerNodes[0])
|
||
|
||
const helpButton = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton.click()
|
||
|
||
const helpPage = comfyPage.page.locator('.sidebar-content-container')
|
||
await expect(helpPage).toContainText('KSampler Help')
|
||
await expect(helpPage).toContainText('This is KSampler documentation')
|
||
|
||
// Now select Checkpoint Loader
|
||
const checkpointNodes = await comfyPage.getNodeRefsByType(
|
||
'CheckpointLoaderSimple'
|
||
)
|
||
await selectNodeWithPan(comfyPage, checkpointNodes[0])
|
||
|
||
// Click help button again
|
||
const helpButton2 = comfyPage.page.locator(
|
||
'.selection-toolbox button[data-testid="info-button"]'
|
||
)
|
||
await helpButton2.click()
|
||
|
||
// Content should update
|
||
await expect(helpPage).toContainText('Checkpoint Loader Help')
|
||
await expect(helpPage).toContainText(
|
||
'This is Checkpoint Loader documentation'
|
||
)
|
||
await expect(helpPage).not.toContainText('KSampler documentation')
|
||
})
|
||
})
|
||
})
|