Compare commits

...

11 Commits

Author SHA1 Message Date
bymyself
0ff153693e fix: use getTitle() to find nodes by display name in getNodeRefsByTitle
The getNodeRefsByTitle function was using n.title === title, which only
works for nodes with an explicitly set title property. Nodes without a
custom title (most nodes) fall back to constructor.title, which is set
from the node's display_name during registration.

Using getTitle() instead properly returns this.title || this.constructor.title,
allowing selectNodes to work with display names like 'VAE Decode' and
'Load Checkpoint'.

Amp-Thread-ID: https://ampcode.com/threads/T-019c17d8-6930-748a-bd55-2370f17af8ea
2026-01-31 22:21:40 -08:00
bymyself
be2693f3b5 fix: revert selectNodes changes and fix test assertions
Amp-Thread-ID: https://ampcode.com/threads/T-019c17a0-1d8c-746f-8ee0-0145b7f56561
2026-01-31 21:59:53 -08:00
bymyself
1d3d64c0eb fix: use getTitle() for node title resolution in getNodeRefsByTitle
Amp-Thread-ID: https://ampcode.com/threads/T-019c17a0-1d8c-746f-8ee0-0145b7f56561
2026-01-31 21:43:20 -08:00
GitHub Action
8cecf0ffe9 [automated] Apply ESLint and Oxfmt fixes 2026-02-01 05:29:20 +00:00
bymyself
32694573a9 fix: correct selectNodes multi-selection and tab name assertions
Amp-Thread-ID: https://ampcode.com/threads/T-019c17a0-1d8c-746f-8ee0-0145b7f56561
2026-01-31 21:27:24 -08:00
GitHub Action
337e71dce2 [automated] Apply ESLint and Oxfmt fixes 2026-02-01 05:00:26 +00:00
bymyself
dca7c218a8 fix: use VAE Decode instead of CLIP Text Encode to avoid strict mode
The default workflow has 2 CLIP Text Encode nodes, causing strict mode
violations. VAE Decode only appears once.

Amp-Thread-ID: https://ampcode.com/threads/T-019c177d-06bf-779a-9c72-51a3626e4659
2026-01-31 20:58:32 -08:00
bymyself
4d11359bea Revert "fix: use selectFirstNodeByTitles to avoid strict mode violations"
This reverts commit 40961820d0.
2026-01-31 20:58:18 -08:00
bymyself
40961820d0 fix: use selectFirstNodeByTitles to avoid strict mode violations
The default workflow has 2 CLIP Text Encode (Prompt) nodes.
Using selectNodes(['KSampler', 'CLIP Text Encode (Prompt)']) selected
all 3 nodes (1 KSampler + 2 CLIP) instead of just 2.

Added selectFirstNodeByTitles helper that only selects the first
matching node for each title, avoiding strict mode violations.

Amp-Thread-ID: https://ampcode.com/threads/T-019c177d-06bf-779a-9c72-51a3626e4659
2026-01-31 20:50:20 -08:00
bymyself
71324f2408 fix: use specific role selectors to avoid strict mode violations
Section headers in Global Settings are uppercase buttons (NODES, CANVAS,
CONNECTION LINKS), not plain text. Using getByRole with exact name avoids
matching tabs and setting labels with similar text.

Amp-Thread-ID: https://ampcode.com/threads/T-019c16eb-8621-7473-9062-a57b0a1e782a
2026-01-31 19:29:46 -08:00
bymyself
610165cde9 test: add e2e tests for properties panel
- Add ComfyPropertiesPanel fixture class with locators and helpers
- Add tests for basic functionality, search, title editing, global settings, and tab navigation
- Tag all tests with @ui for filtering

Amp-Thread-ID: https://ampcode.com/threads/T-019c16eb-8621-7473-9062-a57b0a1e782a
Co-authored-by: Amp <amp@ampcode.com>
2026-01-31 17:57:55 -08:00
8 changed files with 653 additions and 27 deletions

View File

@@ -13,6 +13,7 @@ import { ComfyTemplates } from '../helpers/templates'
import { ComfyMouse } from './ComfyMouse'
import { VueNodeHelpers } from './VueNodeHelpers'
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
import { ComfyPropertiesPanel } from './components/ComfyPropertiesPanel'
import { SettingDialog } from './components/SettingDialog'
import {
NodeLibrarySidebarTab,
@@ -26,18 +27,6 @@ dotenv.config()
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
class ComfyPropertiesPanel {
readonly root: Locator
readonly panelTitle: Locator
readonly searchBox: Locator
constructor(readonly page: Page) {
this.root = page.getByTestId('properties-panel')
this.panelTitle = this.root.locator('h3')
this.searchBox = this.root.getByPlaceholder('Search...')
}
}
class ComfyMenu {
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
private _workflowsTab: WorkflowsSidebarTab | null = null
@@ -170,6 +159,7 @@ export class ComfyPage {
public readonly settingDialog: SettingDialog
public readonly confirmDialog: ConfirmDialog
public readonly vueNodes: VueNodeHelpers
public readonly propertiesPanel: ComfyPropertiesPanel
/** Worker index to test user ID */
public readonly userIds: string[] = []
@@ -202,6 +192,7 @@ export class ComfyPage {
this.settingDialog = new SettingDialog(page, this)
this.confirmDialog = new ConfirmDialog(page)
this.vueNodes = new VueNodeHelpers(page)
this.propertiesPanel = new ComfyPropertiesPanel(page)
}
convertLeafToContent(structure: FolderStructure): FolderStructure {
@@ -1614,7 +1605,7 @@ export class ComfyPage {
(
await this.page.evaluate((title) => {
return window['app'].graph.nodes
.filter((n: LGraphNode) => n.title === title)
.filter((n: LGraphNode) => n.getTitle() === title)
.map((n: LGraphNode) => n.id)
}, title)
).map((id: NodeId) => this.getNodeRefById(id))

View File

@@ -0,0 +1,84 @@
import type { Locator, Page } from '@playwright/test'
export class ComfyPropertiesPanel {
readonly root: Locator
readonly header: Locator
readonly panelTitle: Locator
readonly nodeTitleInput: Locator
readonly closeButton: Locator
readonly searchBox: Locator
readonly tabList: Locator
constructor(readonly page: Page) {
this.root = page.getByTestId('properties-panel')
this.header = this.root.locator('section').first()
this.panelTitle = this.root.locator('h3')
this.nodeTitleInput = this.root.getByTestId('node-title-input')
this.closeButton = this.root.getByRole('button', { name: 'Close' })
this.searchBox = this.root.getByPlaceholder('Search...')
this.tabList = this.root.locator('[role="tablist"]')
}
getTab(tabName: string): Locator {
return this.tabList.getByRole('tab', { name: tabName })
}
async clickTab(tabName: string) {
await this.getTab(tabName).click()
}
async close() {
await this.closeButton.click()
}
async editTitle(newTitle: string) {
await this.panelTitle.click()
await this.nodeTitleInput.fill(newTitle)
await this.nodeTitleInput.press('Enter')
}
async cancelTitleEdit() {
await this.panelTitle.click()
await this.nodeTitleInput.press('Escape')
}
getNodeSection(nodeTitle: string): Locator {
return this.root.locator(`[data-testid="node-section-${nodeTitle}"]`)
}
getAccordionItem(label: string): Locator {
return this.root.locator('.border-b', { hasText: label })
}
get globalSettingsSection() {
return {
nodes: this.getAccordionItem('Nodes'),
canvas: this.getAccordionItem('Canvas'),
connectionLinks: this.getAccordionItem('Connection Links'),
viewAllSettingsButton: this.root.getByRole('button', {
name: 'View all settings'
})
}
}
async toggleSwitch(switchLabel: string) {
const switchContainer = this.root.locator('label', {
hasText: switchLabel
})
const toggle = switchContainer.locator('button[role="switch"]')
await toggle.click()
}
async getSwitchState(switchLabel: string): Promise<boolean> {
const switchContainer = this.root.locator('label', {
hasText: switchLabel
})
const toggle = switchContainer.locator('button[role="switch"]')
const ariaChecked = await toggle.getAttribute('aria-checked')
return ariaChecked === 'true'
}
get noResultsMessage(): Locator {
return this.root.getByText('No results', { exact: false })
}
}

View File

@@ -10,24 +10,18 @@ test.describe('Properties panel', () => {
await expect(propertiesPanel.panelTitle).toContainText('Workflow Overview')
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
await expect(propertiesPanel.panelTitle).toContainText('3 items selected')
await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1)
await expect(
propertiesPanel.root.getByText('CLIP Text Encode (Prompt)')
).toHaveCount(2)
await expect(propertiesPanel.panelTitle).toContainText('2 items selected')
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
await propertiesPanel.searchBox.fill('seed')
await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1)
await expect(
propertiesPanel.root.getByText('CLIP Text Encode (Prompt)')
).toHaveCount(0)
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
await expect(propertiesPanel.root.getByText('VAE Decode')).not.toBeVisible()
await propertiesPanel.searchBox.fill('')
await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1)
await expect(
propertiesPanel.root.getByText('CLIP Text Encode (Prompt)')
).toHaveCount(2)
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
})
})

View File

@@ -0,0 +1,149 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe('Properties panel basic functionality', { tag: ['@ui'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.actionbar.propertiesButton.click()
await expect(comfyPage.propertiesPanel.root).toBeVisible()
})
test.describe('Panel visibility and toggle', () => {
test('opens panel via actionbar button', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await propertiesPanel.close()
await expect(propertiesPanel.root).not.toBeVisible()
await comfyPage.actionbar.propertiesButton.click()
await expect(propertiesPanel.root).toBeVisible()
})
test('closes panel via close button', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await expect(propertiesPanel.root).toBeVisible()
await propertiesPanel.closeButton.click()
await expect(propertiesPanel.root).not.toBeVisible()
})
test('persists open state after page reload', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await expect(propertiesPanel.root).toBeVisible()
await comfyPage.page.reload()
await comfyPage.setup()
await expect(propertiesPanel.root).toBeVisible()
})
})
test.describe('Workflow overview (no selection)', () => {
test('shows "Workflow Overview" title when nothing selected', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await expect(propertiesPanel.panelTitle).toContainText(
'Workflow Overview'
)
})
test('shows Parameters, Nodes, and Global Settings tabs when nothing selected', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
await expect(propertiesPanel.getTab('Global Settings')).toBeVisible()
})
test('Nodes tab displays workflow nodes', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await propertiesPanel.clickTab('Nodes')
await expect(propertiesPanel.root).toContainText('KSampler')
})
})
test.describe('Single node selection', () => {
test('updates title to node name when single node selected', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await expect(propertiesPanel.panelTitle).not.toContainText(
'Workflow Overview'
)
})
test('shows Parameters, Info, and Settings tabs for single node', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
await expect(propertiesPanel.getTab('Info')).toBeVisible()
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
await expect(propertiesPanel.getTab('Nodes')).not.toBeVisible()
})
test('Info tab shows node documentation', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await propertiesPanel.clickTab('Info')
await expect(propertiesPanel.root).toContainText('KSampler')
})
test('shows widget inputs in Parameters tab', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await expect(propertiesPanel.root.getByText('seed')).toBeVisible()
await expect(propertiesPanel.root.getByText('steps')).toBeVisible()
await expect(propertiesPanel.root.getByText('cfg')).toBeVisible()
})
})
test.describe('Multiple node selection', () => {
test('shows item count in title for multiple selections', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
await expect(propertiesPanel.panelTitle).toContainText('items selected')
})
test('shows Nodes and Settings tabs for multiple selection', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
})
test('lists all selected nodes in Nodes tab', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
})
})
})

View File

@@ -0,0 +1,128 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe('Properties panel global settings', { tag: ['@ui'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.actionbar.propertiesButton.click()
await expect(comfyPage.propertiesPanel.root).toBeVisible()
await comfyPage.propertiesPanel.clickTab('Global Settings')
})
test.describe('Global settings sections', () => {
test('displays Nodes section', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await expect(
propertiesPanel.root.getByRole('button', { name: 'NODES' })
).toBeVisible()
})
test('displays Canvas section', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await expect(
propertiesPanel.root.getByRole('button', { name: 'CANVAS' })
).toBeVisible()
})
test('displays Connection Links section', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await expect(
propertiesPanel.root.getByRole('button', { name: 'CONNECTION LINKS' })
).toBeVisible()
})
test('has View all settings button', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
const viewAllButton = propertiesPanel.root.getByRole('button', {
name: /View all settings/i
})
await expect(viewAllButton).toBeVisible()
})
})
test.describe('Nodes section settings', () => {
test('can toggle Show advanced parameters', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
const toggle = propertiesPanel.root
.locator('label', { hasText: 'Show advanced parameters' })
.locator('button[role="switch"]')
await expect(toggle).toBeVisible()
const initialState = await toggle.getAttribute('aria-checked')
await toggle.click()
const newState = await toggle.getAttribute('aria-checked')
expect(newState).not.toBe(initialState)
})
test('can toggle Nodes 2.0 setting', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
const toggle = propertiesPanel.root
.locator('label', { hasText: 'Nodes 2.0' })
.locator('button[role="switch"]')
await expect(toggle).toBeVisible()
})
})
test.describe('Canvas section settings', () => {
test('can adjust grid spacing', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
const gridSpacingLabel = propertiesPanel.root.getByText('Grid Spacing')
await expect(gridSpacingLabel).toBeVisible()
})
test('can toggle Snap nodes to grid', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
const toggle = propertiesPanel.root
.locator('label', { hasText: 'Snap nodes to grid' })
.locator('button[role="switch"]')
await expect(toggle).toBeVisible()
})
})
test.describe('Connection Links section settings', () => {
test('has link shape selector', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
const linkShapeLabel = propertiesPanel.root.getByText('Link Shape')
await expect(linkShapeLabel).toBeVisible()
})
test('can toggle Show connected links', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
const toggle = propertiesPanel.root
.locator('label', { hasText: 'Show connected links' })
.locator('button[role="switch"]')
await expect(toggle).toBeVisible()
})
})
test.describe('View all settings navigation', () => {
test('opens full settings dialog when clicking View all settings', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
const viewAllButton = propertiesPanel.root.getByRole('button', {
name: /View all settings/i
})
await viewAllButton.click()
await expect(comfyPage.settingDialog.root).toBeVisible()
})
})
})

View File

@@ -0,0 +1,71 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe('Properties panel search functionality', { tag: ['@ui'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.actionbar.propertiesButton.click()
await expect(comfyPage.propertiesPanel.root).toBeVisible()
})
test.describe('Search with multiple nodes selected', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
})
test('filters widgets by search query', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await propertiesPanel.searchBox.fill('seed')
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
await expect(
propertiesPanel.root.getByText('VAE Decode')
).not.toBeVisible()
})
test('shows all nodes when search is cleared', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await propertiesPanel.searchBox.fill('seed')
await propertiesPanel.searchBox.fill('')
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
})
test('shows no results message when search matches nothing', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await propertiesPanel.searchBox.fill('nonexistent_widget_xyz')
await expect(propertiesPanel.noResultsMessage).toBeVisible()
})
})
test.describe('Search with single node selected', () => {
test('filters widgets within single node', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await propertiesPanel.searchBox.fill('cfg')
await expect(propertiesPanel.root.getByText('cfg')).toBeVisible()
await expect(
propertiesPanel.root.getByText('seed', { exact: true })
).not.toBeVisible()
})
})
test.describe('Search in workflow overview mode', () => {
test('Nodes tab has search functionality', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await propertiesPanel.clickTab('Nodes')
await expect(propertiesPanel.searchBox).toBeVisible()
})
})
})

View File

@@ -0,0 +1,118 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe('Properties panel tab navigation', { tag: ['@ui'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.actionbar.propertiesButton.click()
await expect(comfyPage.propertiesPanel.root).toBeVisible()
})
test.describe('Tab switching', () => {
test('switches between tabs in workflow overview mode', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
await propertiesPanel.clickTab('Nodes')
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
await propertiesPanel.clickTab('Global Settings')
await expect(
propertiesPanel.root.getByRole('button', { name: 'NODES' })
).toBeVisible()
await expect(
propertiesPanel.root.getByRole('button', { name: 'CANVAS' })
).toBeVisible()
})
test('switches between tabs in single node mode', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
await expect(propertiesPanel.getTab('Info')).toBeVisible()
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
await propertiesPanel.clickTab('Info')
await expect(propertiesPanel.root).toContainText('KSampler')
await propertiesPanel.clickTab('Settings')
await expect(
propertiesPanel.root.getByText('Color').first()
).toBeVisible()
await propertiesPanel.clickTab('Parameters')
await expect(propertiesPanel.root.getByText('seed')).toBeVisible()
})
})
test.describe('Tab availability based on selection', () => {
test('shows different tabs based on selection state', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
await expect(propertiesPanel.getTab('Global Settings')).toBeVisible()
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
await comfyPage.selectNodes(['KSampler'])
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
await expect(propertiesPanel.getTab('Info')).toBeVisible()
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
await expect(propertiesPanel.getTab('Nodes')).not.toBeVisible()
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
await expect(propertiesPanel.getTab('Parameters')).not.toBeVisible()
})
test('first tab updates for multiple selection', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
const firstTab = propertiesPanel.tabList.locator('[role="tab"]').first()
await expect(firstTab).toBeVisible()
})
})
test.describe('Tab persistence', () => {
test('remembers active tab when selection changes', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await propertiesPanel.clickTab('Settings')
await comfyPage.selectNodes(['VAE Decode'])
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
})
test('falls back to default tab when current tab not available', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await propertiesPanel.clickTab('Info')
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
const firstTab = propertiesPanel.tabList.locator('[role="tab"]').first()
await expect(firstTab).toHaveAttribute('aria-selected', 'true')
})
})
})

View File

@@ -0,0 +1,91 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe('Properties panel title editing', { tag: ['@ui'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.actionbar.propertiesButton.click()
await expect(comfyPage.propertiesPanel.root).toBeVisible()
})
test.describe('Single node title editing', () => {
test('shows editable title for single node selection', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
await propertiesPanel.panelTitle.click()
await expect(propertiesPanel.nodeTitleInput).toBeVisible()
})
test('edits node title successfully', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
const newTitle = 'My Custom KSampler'
await propertiesPanel.panelTitle.click()
await propertiesPanel.nodeTitleInput.fill(newTitle)
await propertiesPanel.nodeTitleInput.press('Enter')
await expect(propertiesPanel.panelTitle).toContainText(newTitle)
const renamedNodes = await comfyPage.getNodeRefsByTitle(newTitle)
expect(renamedNodes.length).toBeGreaterThan(0)
})
test('cancels title edit with Escape', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
const originalTitle = await propertiesPanel.panelTitle.innerText()
await propertiesPanel.panelTitle.click()
await propertiesPanel.nodeTitleInput.fill('Should Not Be Saved')
await propertiesPanel.nodeTitleInput.press('Escape')
await expect(propertiesPanel.panelTitle).toContainText(originalTitle)
})
test('does not save empty title', async ({ comfyPage }) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler'])
const originalTitle = await propertiesPanel.panelTitle.innerText()
await propertiesPanel.panelTitle.click()
await propertiesPanel.nodeTitleInput.fill('')
await propertiesPanel.nodeTitleInput.press('Enter')
await expect(propertiesPanel.panelTitle).toContainText(originalTitle)
})
})
test.describe('Title editing restrictions', () => {
test('does not allow title editing for multiple selection', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
await propertiesPanel.panelTitle.click()
await expect(propertiesPanel.nodeTitleInput).not.toBeVisible()
})
test('does not allow title editing for workflow overview', async ({
comfyPage
}) => {
const { propertiesPanel } = comfyPage
await propertiesPanel.panelTitle.click()
await expect(propertiesPanel.nodeTitleInput).not.toBeVisible()
})
})
})